aves_mio1/.flutter/packages/flutter/test/material/snack_bar_test.dart
FabioMich66 19a982ede6
Some checks are pending
Quality check / Flutter analysis (push) Waiting to run
Quality check / CodeQL analysis (java-kotlin) (push) Waiting to run
first commit
2026-03-05 15:51:30 +01:00

4503 lines
162 KiB
Dart

// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// This file is run as part of a reduced test set in CI on Mac and Windows
// machines.
@Tags(<String>['reduced-test-set'])
library;
import 'dart:async';
import 'dart:ui';
import 'package:flutter/foundation.dart';
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 helloSnackBar = 'Hello SnackBar';
const tapTarget = Key('tap-target');
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: Builder(
builder: (BuildContext context) {
return GestureDetector(
key: tapTarget,
onTap: () {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text(helloSnackBar), duration: Duration(seconds: 2)),
);
},
behavior: HitTestBehavior.opaque,
child: const SizedBox(height: 100.0, width: 100.0),
);
},
),
),
),
);
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 {
var snackBarCount = 0;
const tapTarget = Key('tap-target');
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: Builder(
builder: (BuildContext context) {
return GestureDetector(
key: tapTarget,
onTap: () {
snackBarCount += 1;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('bar$snackBarCount'),
duration: const Duration(seconds: 2),
),
);
},
behavior: HitTestBehavior.opaque,
child: const SizedBox(height: 100.0, width: 100.0),
);
},
),
),
),
);
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 {
var snackBarCount = 0;
const tapTarget = Key('tap-target');
late int time;
late ScaffoldFeatureController<SnackBar, SnackBarClosedReason> lastController;
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: Builder(
builder: (BuildContext context) {
return GestureDetector(
key: tapTarget,
onTap: () {
snackBarCount += 1;
lastController = ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('bar$snackBarCount'),
duration: Duration(seconds: time),
),
);
},
behavior: HitTestBehavior.opaque,
child: const SizedBox(height: 100.0, width: 100.0),
);
},
),
),
),
);
expect(find.text('bar1'), findsNothing);
expect(find.text('bar2'), findsNothing);
time = 1000;
await tester.tap(find.byKey(tapTarget)); // queue bar1
final 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 {
const tapTarget = Key('tap-target');
late DismissDirection dismissDirection;
late double width;
var snackBarCount = 0;
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: Builder(
builder: (BuildContext context) {
width = MediaQuery.sizeOf(context).width;
return GestureDetector(
key: tapTarget,
onTap: () {
snackBarCount += 1;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('bar$snackBarCount'),
duration: const Duration(seconds: 2),
dismissDirection: dismissDirection,
),
);
},
behavior: HitTestBehavior.opaque,
child: const SizedBox(height: 100.0, width: 100.0),
);
},
),
),
),
);
await _testSnackBarDismiss(
tester: tester,
tapTarget: tapTarget,
scaffoldWidth: width,
onDismissDirectionChange: (DismissDirection dir) => dismissDirection = dir,
onDragGestureChange: () => snackBarCount = 0,
);
});
testWidgets('SnackBar dismissDirection can be customised from SnackBarThemeData', (
WidgetTester tester,
) async {
const tapTarget = Key('tap-target');
late double width;
await tester.pumpWidget(
MaterialApp(
theme: ThemeData(
snackBarTheme: const SnackBarThemeData(dismissDirection: DismissDirection.startToEnd),
),
home: Scaffold(
body: Builder(
builder: (BuildContext context) {
width = MediaQuery.sizeOf(context).width;
return GestureDetector(
key: tapTarget,
onTap: () {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('swipe ltr'), duration: Duration(seconds: 2)),
);
},
behavior: HitTestBehavior.opaque,
child: const SizedBox(height: 100.0, width: 100.0),
);
},
),
),
),
);
expect(find.text('swipe ltr'), findsNothing);
await tester.tap(find.byKey(tapTarget));
expect(find.text('swipe ltr'), findsNothing);
await tester.pump(); // schedule animation for snack bar
expect(find.text('swipe ltr'), findsOneWidget);
await tester.pump(); // begin animation
expect(find.text('swipe ltr'), findsOneWidget);
await tester.pump(const Duration(milliseconds: 750));
await tester.drag(find.text('swipe ltr'), Offset(width, 0.0));
await tester.pump(); // snack bar dismissed
expect(find.text('swipe ltr'), findsNothing);
});
testWidgets('dismissDirection from SnackBar should be preferred over SnackBarThemeData', (
WidgetTester tester,
) async {
const tapTarget = Key('tap-target');
late double width;
await tester.pumpWidget(
MaterialApp(
theme: ThemeData(
snackBarTheme: const SnackBarThemeData(dismissDirection: DismissDirection.startToEnd),
),
home: Scaffold(
body: Builder(
builder: (BuildContext context) {
width = MediaQuery.sizeOf(context).width;
return GestureDetector(
key: tapTarget,
onTap: () {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('swipe rtl'),
duration: Duration(seconds: 2),
dismissDirection: DismissDirection.endToStart,
),
);
},
behavior: HitTestBehavior.opaque,
child: const SizedBox(height: 100.0, width: 100.0),
);
},
),
),
),
);
expect(find.text('swipe rtl'), findsNothing);
await tester.tap(find.byKey(tapTarget));
expect(find.text('swipe rtl'), findsNothing);
await tester.pump(); // schedule animation for snack bar
expect(find.text('swipe rtl'), findsOneWidget);
await tester.pump(); // begin animation
expect(find.text('swipe rtl'), findsOneWidget);
await tester.pump(const Duration(milliseconds: 750));
await tester.drag(find.text('swipe rtl'), Offset(-width, 0.0));
await tester.pump(); // snack bar dismissed
expect(find.text('swipe rtl'), findsNothing);
});
testWidgets('SnackBar cannot be tapped twice', (WidgetTester tester) async {
var tapCount = 0;
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: Builder(
builder: (BuildContext context) {
return GestureDetector(
onTap: () {
ScaffoldMessenger.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('Material2 - Light theme SnackBar has dark background', (WidgetTester tester) async {
final lightTheme = ThemeData.light(useMaterial3: false);
await tester.pumpWidget(
MaterialApp(
theme: lightTheme,
home: Scaffold(
body: Builder(
builder: (BuildContext context) {
return GestureDetector(
onTap: () {
ScaffoldMessenger.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, isSameColorAs(const Color(0xFF333333)));
});
testWidgets('Material3 - Light theme SnackBar has dark background', (WidgetTester tester) async {
final lightTheme = ThemeData();
await tester.pumpWidget(
MaterialApp(
theme: lightTheme,
home: Scaffold(
body: Builder(
builder: (BuildContext context) {
return GestureDetector(
onTap: () {
ScaffoldMessenger.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 Finder material = find.widgetWithText(Material, 'I am a snack bar.').first;
final RenderPhysicalModel renderModel = tester.renderObject(material);
expect(renderModel.color, equals(lightTheme.colorScheme.inverseSurface));
});
testWidgets('Dark theme SnackBar has light background', (WidgetTester tester) async {
final darkTheme = ThemeData.dark();
await tester.pumpWidget(
MaterialApp(
theme: darkTheme,
home: Scaffold(
body: Builder(
builder: (BuildContext context) {
return GestureDetector(
onTap: () {
ScaffoldMessenger.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('Material2 - Dark theme SnackBar has primary text buttons', (
WidgetTester tester,
) async {
final darkTheme = ThemeData.dark(useMaterial3: false);
await tester.pumpWidget(
MaterialApp(
theme: darkTheme,
home: Scaffold(
body: Builder(
builder: (BuildContext context) {
return GestureDetector(
onTap: () {
ScaffoldMessenger.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 TextStyle buttonTextStyle = tester
.widget<RichText>(find.descendant(of: find.text('ACTION'), matching: find.byType(RichText)))
.text
.style!;
expect(buttonTextStyle.color, equals(darkTheme.colorScheme.primary));
});
testWidgets('Material3 - Dark theme SnackBar has primary text buttons', (
WidgetTester tester,
) async {
final darkTheme = ThemeData.dark();
await tester.pumpWidget(
MaterialApp(
theme: darkTheme,
home: Scaffold(
body: Builder(
builder: (BuildContext context) {
return GestureDetector(
onTap: () {
ScaffoldMessenger.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 TextStyle buttonTextStyle = tester
.widget<RichText>(find.descendant(of: find.text('ACTION'), matching: find.byType(RichText)))
.text
.style!;
expect(buttonTextStyle.color, equals(darkTheme.colorScheme.inversePrimary));
});
testWidgets('SnackBar should inherit theme data from its ancestor', (WidgetTester tester) async {
final theme = ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.green, brightness: Brightness.dark),
);
ThemeData? themeBeforeSnackBar;
ThemeData? themeAfterSnackBar;
await tester.pumpWidget(
MaterialApp(
theme: theme,
home: Scaffold(
body: Builder(
builder: (BuildContext context) {
themeBeforeSnackBar = Theme.of(context);
return GestureDetector(
onTap: () {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Builder(
builder: (BuildContext context) {
themeAfterSnackBar = Theme.of(context);
return 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 ThemeData comparedTheme = themeBeforeSnackBar!.copyWith(
colorScheme: themeAfterSnackBar!.colorScheme,
); // Fields replaced by SnackBar.
expect(comparedTheme, themeAfterSnackBar);
});
testWidgets('Snackbar margin can be customized', (WidgetTester tester) async {
const padding = 20.0;
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: Builder(
builder: (BuildContext context) {
return GestureDetector(
onTap: () {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('I am a snack bar.'),
margin: EdgeInsets.all(padding),
behavior: SnackBarBehavior.floating,
),
);
},
child: const Text('X'),
);
},
),
),
),
);
await tester.tap(find.text('X'));
await tester.pump(); // start animation
await tester.pump(const Duration(milliseconds: 750));
final Finder materialFinder = find.descendant(
of: find.byType(SnackBar),
matching: find.byType(Material),
);
final Offset snackBarBottomLeft = tester.getBottomLeft(materialFinder);
final Offset snackBarBottomRight = tester.getBottomRight(materialFinder);
expect(snackBarBottomLeft.dx, padding);
expect(snackBarBottomLeft.dy, 600 - padding); // Device height is 600.
expect(snackBarBottomRight.dx, 800 - padding); // Device width is 800.
});
testWidgets('SnackbarBehavior.floating is positioned within safe area', (
WidgetTester tester,
) async {
const viewPadding = 50.0;
const floatingSnackBarDefaultBottomMargin = 10.0;
await tester.pumpWidget(
MaterialApp(
home: MediaQuery(
data: const MediaQueryData(
// Simulate non-safe area.
viewPadding: EdgeInsets.only(bottom: viewPadding),
),
child: Scaffold(
body: Builder(
builder: (BuildContext context) {
return GestureDetector(
onTap: () {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('I am a snack bar.'),
behavior: SnackBarBehavior.floating,
),
);
},
child: const Text('X'),
);
},
),
),
),
),
);
await tester.tap(find.text('X'));
await tester.pump(); // Start animation
await tester.pump(const Duration(milliseconds: 750));
final Finder materialFinder = find.descendant(
of: find.byType(SnackBar),
matching: find.byType(Material),
);
final Offset snackBarBottomLeft = tester.getBottomLeft(materialFinder);
expect(
snackBarBottomLeft.dy,
// Device height is 600.
600 - viewPadding - floatingSnackBarDefaultBottomMargin,
);
});
testWidgets('Snackbar padding can be customized', (WidgetTester tester) async {
const padding = 20.0;
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: Builder(
builder: (BuildContext context) {
return GestureDetector(
onTap: () {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('I am a snack bar.'),
padding: EdgeInsets.all(padding),
),
);
},
child: const Text('X'),
);
},
),
),
),
);
await tester.tap(find.text('X'));
await tester.pump(); // start animation
await tester.pump(const Duration(milliseconds: 750));
final Finder textFinder = find.text('I am a snack bar.');
final Finder materialFinder = find.descendant(
of: find.byType(SnackBar),
matching: find.byType(Material),
);
final Offset textBottomLeft = tester.getBottomLeft(textFinder);
final Offset textTopRight = tester.getTopRight(textFinder);
final Offset snackBarBottomLeft = tester.getBottomLeft(materialFinder);
final Offset snackBarTopRight = tester.getTopRight(materialFinder);
expect(textBottomLeft.dx - snackBarBottomLeft.dx, padding);
expect(snackBarTopRight.dx - textTopRight.dx, padding);
expect(snackBarBottomLeft.dy - textBottomLeft.dy, padding);
expect(textTopRight.dy - snackBarTopRight.dy, padding);
});
testWidgets('Snackbar width can be customized', (WidgetTester tester) async {
const width = 200.0;
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: Builder(
builder: (BuildContext context) {
return GestureDetector(
onTap: () {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('I am a snack bar.'),
width: width,
behavior: SnackBarBehavior.floating,
),
);
},
child: const Text('X'),
);
},
),
),
),
);
await tester.tap(find.text('X'));
await tester.pump(); // start animation
await tester.pump(const Duration(milliseconds: 750));
final Finder materialFinder = find.descendant(
of: find.byType(SnackBar),
matching: find.byType(Material),
);
final Offset snackBarBottomLeft = tester.getBottomLeft(materialFinder);
final Offset snackBarBottomRight = tester.getBottomRight(materialFinder);
expect(snackBarBottomLeft.dx, (800 - width) / 2); // Device width is 800.
expect(snackBarBottomRight.dx, (800 + width) / 2); // Device width is 800.
});
testWidgets('Snackbar width can be customized from ThemeData', (WidgetTester tester) async {
const width = 200.0;
await tester.pumpWidget(
MaterialApp(
theme: ThemeData(
snackBarTheme: const SnackBarThemeData(width: width, behavior: SnackBarBehavior.floating),
),
home: Scaffold(
body: Builder(
builder: (BuildContext context) {
return GestureDetector(
onTap: () {
ScaffoldMessenger.of(
context,
).showSnackBar(const SnackBar(content: Text('Feeling snackish')));
},
child: const Text('X'),
);
},
),
),
),
);
await tester.tap(find.text('X'));
await tester.pump(); // start animation
await tester.pump(const Duration(milliseconds: 750));
final Finder materialFinder = find.descendant(
of: find.byType(SnackBar),
matching: find.byType(Material),
);
final Offset snackBarBottomLeft = tester.getBottomLeft(materialFinder);
final Offset snackBarBottomRight = tester.getBottomRight(materialFinder);
expect(snackBarBottomLeft.dx, (800 - width) / 2); // Device width is 800.
expect(snackBarBottomRight.dx, (800 + width) / 2); // Device width is 800.
});
testWidgets('Snackbar width customization takes preference of widget over theme', (
WidgetTester tester,
) async {
const themeWidth = 200.0;
const widgetWidth = 400.0;
await tester.pumpWidget(
MaterialApp(
theme: ThemeData(
snackBarTheme: const SnackBarThemeData(
width: themeWidth,
behavior: SnackBarBehavior.floating,
),
),
home: Scaffold(
body: Builder(
builder: (BuildContext context) {
return GestureDetector(
onTap: () {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Feeling super snackish'), width: widgetWidth),
);
},
child: const Text('X'),
);
},
),
),
),
);
await tester.tap(find.text('X'));
await tester.pump(); // start animation
await tester.pump(const Duration(milliseconds: 750));
final Finder materialFinder = find.descendant(
of: find.byType(SnackBar),
matching: find.byType(Material),
);
final Offset snackBarBottomLeft = tester.getBottomLeft(materialFinder);
final Offset snackBarBottomRight = tester.getBottomRight(materialFinder);
expect(snackBarBottomLeft.dx, (800 - widgetWidth) / 2); // Device width is 800.
expect(snackBarBottomRight.dx, (800 + widgetWidth) / 2); // Device width is 800.
});
testWidgets('Material2 - Snackbar labels can be colored as MaterialColor', (
WidgetTester tester,
) async {
await tester.pumpWidget(
MaterialApp(
theme: ThemeData(useMaterial3: false),
home: Scaffold(
body: Builder(
builder: (BuildContext context) {
return GestureDetector(
onTap: () {
ScaffoldMessenger.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'));
expect(actionTextBox.widget, isA<Text>());
final Text(:TextStyle? style) = actionTextBox.widget as Text;
final TextStyle defaultStyle = DefaultTextStyle.of(actionTextBox).style;
expect(defaultStyle.merge(style).color, Colors.lightBlue);
});
testWidgets('Material3 - Snackbar labels can be colored as MaterialColor', (
WidgetTester tester,
) async {
const MaterialColor usedColor = Colors.teal;
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: Builder(
builder: (BuildContext context) {
return GestureDetector(
onTap: () {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: const Text('I am a snack bar.'),
duration: const Duration(seconds: 2),
action: SnackBarAction(
textColor: usedColor,
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 actionTextButton = tester.element(find.widgetWithText(TextButton, 'ACTION'));
final Widget textButton = actionTextButton.widget;
if (textButton is TextButton) {
final ButtonStyle buttonStyle = textButton.style!;
if (buttonStyle.foregroundColor is WidgetStateColor) {
// Same color when resolved
expect(buttonStyle.foregroundColor!.resolve(<WidgetState>{}), usedColor);
} else {
expect(false, true);
}
} else {
expect(false, true);
}
});
testWidgets('Snackbar labels can be colored as WidgetStateColor (Material 3)', (
WidgetTester tester,
) async {
const usedColor = _TestMaterialStateColor();
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: Builder(
builder: (BuildContext context) {
return GestureDetector(
onTap: () {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: const Text('I am a snack bar.'),
duration: const Duration(seconds: 2),
action: SnackBarAction(
textColor: usedColor,
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 actionTextButton = tester.element(find.widgetWithText(TextButton, 'ACTION'));
final Widget textButton = actionTextButton.widget;
if (textButton is TextButton) {
final ButtonStyle buttonStyle = textButton.style!;
if (buttonStyle.foregroundColor is WidgetStateColor) {
// Exactly the same object
expect(buttonStyle.foregroundColor, usedColor);
} else {
expect(false, true);
}
} else {
expect(false, true);
}
});
testWidgets('Material2 - SnackBar button text alignment', (WidgetTester tester) async {
await tester.pumpWidget(
MaterialApp(
theme: ThemeData(useMaterial3: false),
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: () {
ScaffoldMessenger.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 + 12.0); // action padding + margin
expect(
snackBarBottomRight.dx - actionTextBottomRight.dx,
24.0 + 12.0 + 30.0,
); // action (padding + margin) + right padding
expect(
snackBarBottomRight.dy - actionTextBottomRight.dy,
17.0 + 40.0,
); // margin + bottom padding
});
testWidgets('Material3 - 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: () {
ScaffoldMessenger.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, 14.0 + 40.0); // margin + bottom padding
expect(actionTextBottomLeft.dx - textBottomRight.dx, 24.0 + 12.0); // action padding + margin
expect(
snackBarBottomRight.dx - actionTextBottomRight.dx,
24.0 + 12.0 + 30.0,
); // action (padding + margin) + right padding
expect(
snackBarBottomRight.dy - actionTextBottomRight.dy,
14.0 + 40.0,
); // margin + bottom padding
});
testWidgets(
'Material2 - Custom padding between SnackBar and its contents when set to SnackBarBehavior.fixed',
(WidgetTester tester) async {
await tester.pumpWidget(
MaterialApp(
theme: ThemeData(useMaterial3: false),
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), label: 'Animutation'),
BottomNavigationBarItem(icon: Icon(Icons.block), label: 'Zombo.com'),
],
),
body: Builder(
builder: (BuildContext context) {
return GestureDetector(
onTap: () {
ScaffoldMessenger.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 + 12.0); // action padding + margin
expect(
snackBarBottomRight.dx - actionTextBottomRight.dx,
24.0 + 12.0 + 30.0,
); // action (padding + margin) + right padding
expect(
snackBarBottomRight.dy - actionTextBottomRight.dy,
17.0,
); // margin (with no bottom padding)
},
);
testWidgets(
'Material3 - Custom padding between SnackBar and its contents when set to SnackBarBehavior.fixed',
(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), label: 'Animutation'),
BottomNavigationBarItem(icon: Icon(Icons.block), label: 'Zombo.com'),
],
),
body: Builder(
builder: (BuildContext context) {
return GestureDetector(
onTap: () {
ScaffoldMessenger.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, 14.0); // margin (with no bottom padding)
expect(actionTextBottomLeft.dx - textBottomRight.dx, 24.0 + 12.0); // action padding + margin
expect(
snackBarBottomRight.dx - actionTextBottomRight.dx,
24.0 + 12.0 + 30.0,
); // action (padding + margin) + right padding
expect(
snackBarBottomRight.dy - actionTextBottomRight.dy,
14.0,
); // margin (with no bottom padding)
},
);
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: () {
ScaffoldMessenger.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'),
);
},
),
),
),
),
);
// Get the Rect of the FAB to compare after the SnackBar appears.
final Rect originalFabRect = tester.getRect(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 Rect fabRect = tester.getRect(find.byType(FloatingActionButton));
// FAB should shift upwards after SnackBar appears.
expect(fabRect.center.dy, lessThan(originalFabRect.center.dy));
final Offset snackBarTopRight = tester.getTopRight(find.byType(SnackBar));
// FAB's surrounding padding is set to [kFloatingActionButtonMargin] in floating_action_button_location.dart by default.
const defaultFabPadding = 16;
// FAB should be positioned above the SnackBar by the default padding.
expect(fabRect.bottomRight.dy, snackBarTopRight.dy - defaultFabPadding);
});
testWidgets('Material2 - Floating SnackBar button text alignment', (WidgetTester tester) async {
await tester.pumpWidget(
MaterialApp(
theme: ThemeData(
useMaterial3: false,
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: () {
ScaffoldMessenger.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 + 8.0); // action padding + margin
expect(
snackBarBottomRight.dx - actionTextBottomRight.dx,
31.0 + 30.0 + 8.0,
); // margin + right (padding + margin)
expect(
snackBarBottomRight.dy - actionTextBottomRight.dy,
27.0,
); // margin (with no bottom padding)
});
testWidgets('Material3 - 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: () {
ScaffoldMessenger.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, 24.0); // margin (with no bottom padding)
expect(actionTextBottomLeft.dx - textBottomRight.dx, 16.0 + 8.0); // action padding + margin
expect(
snackBarBottomRight.dx - actionTextBottomRight.dx,
31.0 + 30.0 + 8.0,
); // margin + right (padding + margin)
expect(
snackBarBottomRight.dy - actionTextBottomRight.dy,
24.0,
); // margin (with no bottom padding)
});
testWidgets(
'Material2 - Custom padding between SnackBar and its contents when set to SnackBarBehavior.floating',
(WidgetTester tester) async {
await tester.pumpWidget(
MaterialApp(
theme: ThemeData(
useMaterial3: false,
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), label: 'Animutation'),
BottomNavigationBarItem(icon: Icon(Icons.block), label: 'Zombo.com'),
],
),
body: Builder(
builder: (BuildContext context) {
return GestureDetector(
onTap: () {
ScaffoldMessenger.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 + 8.0); // action (margin + padding)
expect(
snackBarBottomRight.dx - actionTextBottomRight.dx,
31.0 + 30.0 + 8.0,
); // margin + right (padding + margin)
expect(
snackBarBottomRight.dy - actionTextBottomRight.dy,
27.0,
); // margin (with no bottom padding)
},
);
testWidgets(
'Material3 - Custom padding between SnackBar and its contents when set to SnackBarBehavior.floating',
(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), label: 'Animutation'),
BottomNavigationBarItem(icon: Icon(Icons.block), label: 'Zombo.com'),
],
),
body: Builder(
builder: (BuildContext context) {
return GestureDetector(
onTap: () {
ScaffoldMessenger.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, 24.0); // margin (with no bottom padding)
expect(actionTextBottomLeft.dx - textBottomRight.dx, 16.0 + 8.0); // action (margin + padding)
expect(
snackBarBottomRight.dx - actionTextBottomRight.dx,
31.0 + 30.0 + 8.0,
); // margin + right (padding + margin)
expect(
snackBarBottomRight.dy - actionTextBottomRight.dy,
24.0,
); // margin (with no bottom padding)
},
);
testWidgets('SnackBarClosedReason', (WidgetTester tester) async {
final scaffoldMessengerKey = GlobalKey<ScaffoldMessengerState>();
var actionPressed = false;
SnackBarClosedReason? closedReason;
await tester.pumpWidget(
MaterialApp(
scaffoldMessengerKey: scaffoldMessengerKey,
home: Scaffold(
body: Builder(
builder: (BuildContext context) {
return GestureDetector(
onTap: () {
ScaffoldMessenger.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));
scaffoldMessengerKey.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));
scaffoldMessengerKey.currentState!.hideCurrentSnackBar();
await tester.pumpAndSettle(const Duration(seconds: 1));
expect(closedReason, equals(SnackBarClosedReason.hide));
// Remove action to test SnackBarClosedReason.timeout because Snackbar with
// action doesn't timeout.
await tester.pumpWidget(
MaterialApp(
scaffoldMessengerKey: scaffoldMessengerKey,
home: Scaffold(
body: Builder(
builder: (BuildContext context) {
return GestureDetector(
onTap: () {
ScaffoldMessenger.of(context)
.showSnackBar(
const SnackBar(content: Text('snack'), duration: Duration(seconds: 2)),
)
.closed
.then<void>((SnackBarClosedReason reason) {
closedReason = reason;
});
},
child: const Text('X'),
);
},
),
),
),
);
// 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 scaffoldKey = GlobalKey<ScaffoldState>();
await tester.pumpWidget(
MaterialApp(
home: MediaQuery(
data: const MediaQueryData(accessibleNavigation: true),
child: ScaffoldMessenger(
child: Builder(
builder: (BuildContext context) {
return Scaffold(
key: scaffoldKey,
body: GestureDetector(
onTap: () {
ScaffoldMessenger.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 scaffoldKey = GlobalKey<ScaffoldState>();
await tester.pumpWidget(
MaterialApp(
home: MediaQuery(
data: const MediaQueryData(accessibleNavigation: true),
child: ScaffoldMessenger(
child: Builder(
builder: (BuildContext context) {
return Scaffold(
key: scaffoldKey,
body: GestureDetector(
onTap: () {
ScaffoldMessenger.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 helloSnackBar = 'Hello SnackBar';
const tapTarget = Key('tap-target');
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: Builder(
builder: (BuildContext context) {
return GestureDetector(
key: tapTarget,
onTap: () {
ScaffoldMessenger.of(
context,
).showSnackBar(const SnackBar(content: Text(helloSnackBar)));
},
behavior: HitTestBehavior.opaque,
child: const SizedBox(height: 100.0, width: 100.0),
);
},
),
),
),
);
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({required bool accessibleNavigation}) {
return tester.pumpWidget(
MediaQuery(
data: MediaQueryData(accessibleNavigation: accessibleNavigation),
child: MaterialApp(
home: Scaffold(
body: Builder(
builder: (BuildContext context) {
return GestureDetector(
onTap: () {
ScaffoldMessenger.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'), findsOneWidget);
await tester.tap(find.text('foo'));
await tester.pumpAndSettle();
expect(find.text('test'), findsNothing);
});
testWidgets('Snackbar calls onVisible once', (WidgetTester tester) async {
const tapTarget = Key('tap-target');
var called = 0;
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: Builder(
builder: (BuildContext context) {
return GestureDetector(
key: tapTarget,
onTap: () {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: const Text('hello'),
duration: const Duration(seconds: 1),
onVisible: () {
called += 1;
},
),
);
},
behavior: HitTestBehavior.opaque,
child: const SizedBox(height: 100.0, width: 100.0),
);
},
),
),
),
);
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 tapTarget = Key('tap-target');
var called = 0;
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: Builder(
builder: (BuildContext context) {
return GestureDetector(
key: tapTarget,
onTap: () {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: const Text('hello'),
duration: const Duration(seconds: 1),
onVisible: () {
called += 1;
},
),
);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: const Text('hello 2'),
duration: const Duration(seconds: 1),
onVisible: () {
called += 1;
},
),
);
},
behavior: HitTestBehavior.opaque,
child: const SizedBox(height: 100.0, width: 100.0),
);
},
),
),
),
);
await tester.tap(find.byKey(tapTarget));
await tester.pump(); // start animation
await tester.pumpAndSettle();
expect(find.text('hello'), findsOneWidget);
expect(called, 1);
});
group('SnackBar position', () {
for (final SnackBarBehavior behavior in SnackBarBehavior.values) {
final snackBar = SnackBar(content: const Text('SnackBar text'), behavior: behavior);
testWidgets('$behavior should align SnackBar with the bottom of Scaffold '
'when Scaffold has no other elements', (WidgetTester tester) async {
await tester.pumpWidget(MaterialApp(home: Scaffold(body: Container())));
final ScaffoldMessengerState scaffoldMessengerState = tester.state(
find.byType(ScaffoldMessenger),
);
scaffoldMessengerState.showSnackBar(snackBar);
await tester.pumpAndSettle(); // Have the SnackBar fully animate out.
final Offset snackBarBottomRight = tester.getBottomRight(find.byType(SnackBar));
final Offset scaffoldBottomRight = tester.getBottomRight(find.byType(Scaffold));
expect(snackBarBottomRight, equals(scaffoldBottomRight));
final Offset snackBarBottomLeft = tester.getBottomLeft(find.byType(SnackBar));
final Offset scaffoldBottomLeft = tester.getBottomLeft(find.byType(Scaffold));
expect(snackBarBottomLeft, equals(scaffoldBottomLeft));
});
testWidgets('$behavior should align SnackBar with the top of BottomNavigationBar '
'when Scaffold has no FloatingActionButton', (WidgetTester tester) async {
final boxKey = UniqueKey();
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: Container(),
bottomNavigationBar: SizedBox(key: boxKey, width: 800, height: 60),
),
),
);
final ScaffoldMessengerState scaffoldMessengerState = tester.state(
find.byType(ScaffoldMessenger),
);
scaffoldMessengerState.showSnackBar(snackBar);
await tester.pumpAndSettle(); // Have the SnackBar fully animate out.
final Offset snackBarBottomRight = tester.getBottomRight(find.byType(SnackBar));
final Offset bottomNavigationBarTopRight = tester.getTopRight(find.byKey(boxKey));
expect(snackBarBottomRight, equals(bottomNavigationBarTopRight));
final Offset snackBarBottomLeft = tester.getBottomLeft(find.byType(SnackBar));
final Offset bottomNavigationBarTopLeft = tester.getTopLeft(find.byKey(boxKey));
expect(snackBarBottomLeft, equals(bottomNavigationBarTopLeft));
});
}
testWidgets('Padding of ${SnackBarBehavior.fixed} is not consumed by viewInsets', (
WidgetTester tester,
) async {
final Widget child = MaterialApp(
home: Scaffold(
resizeToAvoidBottomInset: false,
floatingActionButton: FloatingActionButton(
child: const Icon(Icons.send),
onPressed: () {},
),
body: Builder(
builder: (BuildContext context) {
return GestureDetector(
onTap: () {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: const Text('I am a snack bar.'),
duration: const Duration(seconds: 2),
action: SnackBarAction(label: 'ACTION', onPressed: () {}),
behavior: SnackBarBehavior.fixed,
),
);
},
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 initialBottomLeft = tester.getBottomLeft(find.byType(SnackBar));
final Offset initialBottomRight = tester.getBottomRight(find.byType(SnackBar));
// Consume bottom padding - as if by the keyboard opening
await tester.pumpWidget(
MediaQuery(
data: const MediaQueryData(
viewPadding: EdgeInsets.all(20),
viewInsets: EdgeInsets.all(100),
),
child: child,
),
);
await tester.tap(find.text('X'));
await tester.pumpAndSettle(); // Have the SnackBar fully animate out.
final Offset finalBottomLeft = tester.getBottomLeft(find.byType(SnackBar));
final Offset finalBottomRight = tester.getBottomRight(find.byType(SnackBar));
expect(initialBottomLeft, finalBottomLeft);
expect(initialBottomRight, finalBottomRight);
});
testWidgets('${SnackBarBehavior.fixed} should align SnackBar with the bottom of Scaffold '
'when Scaffold has a FloatingActionButton', (WidgetTester tester) async {
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: Container(),
floatingActionButton: FloatingActionButton(onPressed: () {}),
),
),
);
final ScaffoldMessengerState scaffoldMessengerState = tester.state(
find.byType(ScaffoldMessenger),
);
scaffoldMessengerState.showSnackBar(
const SnackBar(content: Text('Snackbar text'), behavior: SnackBarBehavior.fixed),
);
await tester.pumpAndSettle(); // Have the SnackBar fully animate out.
final Offset snackBarBottomRight = tester.getBottomRight(find.byType(SnackBar));
final Offset scaffoldBottomRight = tester.getBottomRight(find.byType(Scaffold));
expect(snackBarBottomRight, equals(scaffoldBottomRight));
final Offset snackBarBottomLeft = tester.getBottomLeft(find.byType(SnackBar));
final Offset scaffoldBottomLeft = tester.getBottomLeft(find.byType(Scaffold));
expect(snackBarBottomLeft, equals(scaffoldBottomLeft));
});
testWidgets(
'${SnackBarBehavior.floating} should align SnackBar with the top of FloatingActionButton when Scaffold has a FloatingActionButton',
(WidgetTester tester) async {
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
floatingActionButton: FloatingActionButton(
child: const Icon(Icons.send),
onPressed: () {},
),
body: Builder(
builder: (BuildContext context) {
return GestureDetector(
onTap: () {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: const Text('I am a snack bar.'),
duration: const Duration(seconds: 2),
action: SnackBarAction(label: 'ACTION', onPressed: () {}),
behavior: SnackBarBehavior.floating,
),
);
},
child: const Text('X'),
);
},
),
),
),
);
await tester.tap(find.text('X'));
await tester.pumpAndSettle(); // Have the SnackBar fully animate out.
final Offset snackBarBottomLeft = tester.getBottomLeft(find.byType(SnackBar));
final Offset floatingActionButtonTopLeft = tester.getTopLeft(
find.byType(FloatingActionButton),
);
// Since padding between the SnackBar and the FAB is created by the SnackBar,
// the bottom offset of the SnackBar should be equal to the top offset of the FAB
expect(snackBarBottomLeft.dy, floatingActionButtonTopLeft.dy);
},
);
testWidgets(
'${SnackBarBehavior.floating} should not align SnackBar with the top of FloatingActionButton '
'when Scaffold has a FloatingActionButton and floatingActionButtonLocation is set to a top position',
(WidgetTester tester) async {
Future<void> pumpApp({required FloatingActionButtonLocation fabLocation}) async {
return tester.pumpWidget(
MaterialApp(
home: Scaffold(
floatingActionButton: FloatingActionButton(
child: const Icon(Icons.send),
onPressed: () {},
),
floatingActionButtonLocation: fabLocation,
body: Builder(
builder: (BuildContext context) {
return GestureDetector(
onTap: () {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: const Text('I am a snack bar.'),
duration: const Duration(seconds: 2),
action: SnackBarAction(label: 'ACTION', onPressed: () {}),
behavior: SnackBarBehavior.floating,
),
);
},
child: const Text('X'),
);
},
),
),
),
);
}
const topLocations = <FloatingActionButtonLocation>[
FloatingActionButtonLocation.startTop,
FloatingActionButtonLocation.centerTop,
FloatingActionButtonLocation.endTop,
FloatingActionButtonLocation.miniStartTop,
FloatingActionButtonLocation.miniCenterTop,
FloatingActionButtonLocation.miniEndTop,
];
for (final location in topLocations) {
await pumpApp(fabLocation: location);
await tester.tap(find.text('X'));
await tester.pumpAndSettle(); // Have the SnackBar fully animate out.
final Offset snackBarBottomLeft = tester.getBottomLeft(find.byType(SnackBar));
expect(snackBarBottomLeft.dy, 600); // Device height is 600.
}
},
);
testWidgets(
'${SnackBarBehavior.floating} should align SnackBar with the top of FloatingActionButton '
'when Scaffold has a FloatingActionButton and floatingActionButtonLocation is not set to a top position',
(WidgetTester tester) async {
Future<void> pumpApp({required FloatingActionButtonLocation fabLocation}) async {
return tester.pumpWidget(
MaterialApp(
home: Scaffold(
floatingActionButton: FloatingActionButton(
child: const Icon(Icons.send),
onPressed: () {},
),
floatingActionButtonLocation: fabLocation,
body: Builder(
builder: (BuildContext context) {
return GestureDetector(
onTap: () {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: const Text('I am a snack bar.'),
duration: const Duration(seconds: 2),
action: SnackBarAction(label: 'ACTION', onPressed: () {}),
behavior: SnackBarBehavior.floating,
),
);
},
child: const Text('X'),
);
},
),
),
),
);
}
const nonTopLocations = <FloatingActionButtonLocation>[
FloatingActionButtonLocation.startDocked,
FloatingActionButtonLocation.startFloat,
FloatingActionButtonLocation.centerDocked,
FloatingActionButtonLocation.centerFloat,
FloatingActionButtonLocation.endContained,
FloatingActionButtonLocation.endDocked,
FloatingActionButtonLocation.endFloat,
FloatingActionButtonLocation.miniStartDocked,
FloatingActionButtonLocation.miniStartFloat,
FloatingActionButtonLocation.miniCenterDocked,
FloatingActionButtonLocation.miniCenterFloat,
FloatingActionButtonLocation.miniEndDocked,
FloatingActionButtonLocation.miniEndFloat,
// Regression test related to https://github.com/flutter/flutter/pull/131303.
_CustomFloatingActionButtonLocation(),
];
for (final location in nonTopLocations) {
await pumpApp(fabLocation: location);
await tester.tap(find.text('X'));
await tester.pumpAndSettle(); // Have the SnackBar fully animate out.
final Offset snackBarBottomLeft = tester.getBottomLeft(find.byType(SnackBar));
final Offset floatingActionButtonTopLeft = tester.getTopLeft(
find.byType(FloatingActionButton),
);
// Since padding between the SnackBar and the FAB is created by the SnackBar,
// the bottom offset of the SnackBar should be equal to the top offset of the FAB
expect(snackBarBottomLeft.dy, floatingActionButtonTopLeft.dy);
}
},
);
testWidgets(
'${SnackBarBehavior.fixed} should align SnackBar with the top of BottomNavigationBar '
'when Scaffold has a BottomNavigationBar and FloatingActionButton',
(WidgetTester tester) async {
final boxKey = UniqueKey();
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: Container(),
bottomNavigationBar: SizedBox(key: boxKey, width: 800, height: 60),
floatingActionButton: FloatingActionButton(onPressed: () {}),
),
),
);
final ScaffoldMessengerState scaffoldMessengerState = tester.state(
find.byType(ScaffoldMessenger),
);
scaffoldMessengerState.showSnackBar(
const SnackBar(content: Text('SnackBar text'), behavior: SnackBarBehavior.fixed),
);
await tester.pumpAndSettle(); // Have the SnackBar fully animate out.
final Offset snackBarBottomRight = tester.getBottomRight(find.byType(SnackBar));
final Offset bottomNavigationBarTopRight = tester.getTopRight(find.byKey(boxKey));
expect(snackBarBottomRight, equals(bottomNavigationBarTopRight));
final Offset snackBarBottomLeft = tester.getBottomLeft(find.byType(SnackBar));
final Offset bottomNavigationBarTopLeft = tester.getTopLeft(find.byKey(boxKey));
expect(snackBarBottomLeft, equals(bottomNavigationBarTopLeft));
},
);
testWidgets(
'${SnackBarBehavior.floating} should align SnackBar with the top of FloatingActionButton '
'when Scaffold has BottomNavigationBar and FloatingActionButton',
(WidgetTester tester) async {
final boxKey = UniqueKey();
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: Container(),
bottomNavigationBar: SizedBox(key: boxKey, width: 800, height: 60),
floatingActionButton: FloatingActionButton(onPressed: () {}),
),
),
);
final ScaffoldMessengerState scaffoldMessengerState = tester.state(
find.byType(ScaffoldMessenger),
);
scaffoldMessengerState.showSnackBar(
const SnackBar(content: Text('SnackBar text'), behavior: SnackBarBehavior.floating),
);
await tester.pumpAndSettle(); // Have the SnackBar fully animate out.
final Offset snackBarBottomRight = tester.getBottomRight(find.byType(SnackBar));
final Offset fabTopRight = tester.getTopRight(find.byType(FloatingActionButton));
expect(snackBarBottomRight.dy, equals(fabTopRight.dy));
},
);
testWidgets(
'${SnackBarBehavior.floating} should align SnackBar with the top of BottomNavigationBar '
'when Scaffold has both BottomNavigationBar and FloatingActionButton and '
'BottomNavigationBar.top is higher than FloatingActionButton.top',
(WidgetTester tester) async {
final boxKey = UniqueKey();
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: Container(),
bottomNavigationBar: SizedBox(key: boxKey, width: 800, height: 200),
floatingActionButton: FloatingActionButton(onPressed: () {}),
floatingActionButtonLocation: FloatingActionButtonLocation.endContained,
),
),
);
final ScaffoldMessengerState scaffoldMessengerState = tester.state(
find.byType(ScaffoldMessenger),
);
scaffoldMessengerState.showSnackBar(
const SnackBar(content: Text('SnackBar text'), behavior: SnackBarBehavior.floating),
);
await tester.pumpAndSettle(); // Have the SnackBar fully animate out.
final Offset snackBarBottomRight = tester.getBottomRight(find.byType(SnackBar));
final Offset fabTopRight = tester.getTopRight(find.byType(FloatingActionButton));
final Offset navBarTopRight = tester.getTopRight(find.byKey(boxKey));
// Test the top of the navigation bar is higher than the top of the floating action button.
expect(fabTopRight.dy, greaterThan(navBarTopRight.dy));
expect(snackBarBottomRight.dy, equals(navBarTopRight.dy));
},
);
Future<void> openFloatingSnackBar(WidgetTester tester) async {
final ScaffoldMessengerState scaffoldMessengerState = tester.state(
find.byType(ScaffoldMessenger),
);
scaffoldMessengerState.showSnackBar(
const SnackBar(content: Text('SnackBar text'), behavior: SnackBarBehavior.floating),
);
await tester.pumpAndSettle(); // Have the SnackBar fully animate out.
}
const offScreenMessage =
'Floating SnackBar presented off screen.\n'
'A SnackBar with behavior property set to SnackBarBehavior.floating is fully '
'or partially off screen because some or all the widgets provided to '
'Scaffold.floatingActionButton, Scaffold.persistentFooterButtons and '
'Scaffold.bottomNavigationBar take up too much vertical space.\n'
'Consider constraining the size of these widgets to allow room for the SnackBar to be visible.';
testWidgets(
'Snackbar with SnackBarBehavior.floating will assert when offset too high by a large Scaffold.floatingActionButton',
(WidgetTester tester) async {
// Regression test for https://github.com/flutter/flutter/issues/84263
Future<void> boilerplate({required double? fabHeight}) {
return tester.pumpWidget(
MaterialApp(
home: Scaffold(floatingActionButton: Container(height: fabHeight)),
),
);
}
// Run once with a visible SnackBar to compute the empty space above SnackBar.
const double mediumFabHeight = 100;
await boilerplate(fabHeight: mediumFabHeight);
await openFloatingSnackBar(tester);
expect(tester.takeException(), isNull);
final double spaceAboveSnackBar = tester.getTopLeft(find.byType(SnackBar)).dy;
// Run with the Snackbar fully off screen.
await boilerplate(fabHeight: spaceAboveSnackBar + mediumFabHeight * 2);
await openFloatingSnackBar(tester);
var exception = tester.takeException() as AssertionError;
expect(exception.message, offScreenMessage);
// Run with the Snackbar partially off screen.
await boilerplate(fabHeight: spaceAboveSnackBar + mediumFabHeight + 10);
await openFloatingSnackBar(tester);
exception = tester.takeException() as AssertionError;
expect(exception.message, offScreenMessage);
// Run with the Snackbar fully visible right on the top of the screen.
await boilerplate(fabHeight: spaceAboveSnackBar + mediumFabHeight);
await openFloatingSnackBar(tester);
expect(tester.takeException(), isNull);
},
);
testWidgets(
'Material2 - Snackbar with SnackBarBehavior.floating will assert when offset too high by a large Scaffold.persistentFooterButtons',
(WidgetTester tester) async {
// Regression test for https://github.com/flutter/flutter/issues/84263
await tester.pumpWidget(
MaterialApp(
theme: ThemeData(useMaterial3: false),
home: const Scaffold(persistentFooterButtons: <Widget>[SizedBox(height: 1000)]),
),
);
await openFloatingSnackBar(tester);
await tester.pumpAndSettle(); // Have the SnackBar fully animate out.
final exception = tester.takeException() as AssertionError;
expect(exception.message, offScreenMessage);
},
);
testWidgets(
'Material3 - Snackbar with SnackBarBehavior.floating will assert when offset too high by a large Scaffold.persistentFooterButtons',
(WidgetTester tester) async {
// Regression test for https://github.com/flutter/flutter/issues/84263
await tester.pumpWidget(
const MaterialApp(
home: Scaffold(persistentFooterButtons: <Widget>[SizedBox(height: 1000)]),
),
);
final FlutterExceptionHandler? handler = FlutterError.onError;
final errorMessages = <String>[];
FlutterError.onError = (FlutterErrorDetails details) {
errorMessages.add(details.exceptionAsString());
};
addTearDown(() => FlutterError.onError = handler);
await openFloatingSnackBar(tester);
await tester.pumpAndSettle(); // Have the SnackBar fully animate out.
expect(errorMessages.contains(offScreenMessage), isTrue);
},
);
testWidgets(
'Material2 - Snackbar with SnackBarBehavior.floating will assert when offset too high by a large Scaffold.bottomNavigationBar',
(WidgetTester tester) async {
// Regression test for https://github.com/flutter/flutter/issues/84263
await tester.pumpWidget(
MaterialApp(
theme: ThemeData(useMaterial3: false),
home: const Scaffold(bottomNavigationBar: SizedBox(height: 1000)),
),
);
await openFloatingSnackBar(tester);
await tester.pumpAndSettle(); // Have the SnackBar fully animate out.
final exception = tester.takeException() as AssertionError;
expect(exception.message, offScreenMessage);
},
);
testWidgets(
'Material3 - Snackbar with SnackBarBehavior.floating will assert when offset too high by a large Scaffold.bottomNavigationBar',
(WidgetTester tester) async {
// Regression test for https://github.com/flutter/flutter/issues/84263
await tester.pumpWidget(
const MaterialApp(home: Scaffold(bottomNavigationBar: SizedBox(height: 1000))),
);
final FlutterExceptionHandler? handler = FlutterError.onError;
final errorMessages = <String>[];
FlutterError.onError = (FlutterErrorDetails details) {
errorMessages.add(details.exceptionAsString());
};
addTearDown(() => FlutterError.onError = handler);
await openFloatingSnackBar(tester);
await tester.pumpAndSettle(); // Have the SnackBar fully animate out.
expect(errorMessages.contains(offScreenMessage), isTrue);
},
);
testWidgets('SnackBar has correct end padding when it contains an action with fixed behavior', (
WidgetTester tester,
) async {
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: Builder(
builder: (BuildContext context) {
return GestureDetector(
onTap: () {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: const Text('Some content'),
behavior: SnackBarBehavior.fixed,
action: SnackBarAction(label: 'ACTION', onPressed: () {}),
),
);
},
child: const Text('X'),
);
},
),
),
),
);
await tester.tap(find.text('X'));
await tester.pumpAndSettle();
final Offset snackBarTopRight = tester.getTopRight(find.byType(SnackBar));
final Offset actionTopRight = tester.getTopRight(find.byType(SnackBarAction));
expect(snackBarTopRight.dx - actionTopRight.dx, 12.0);
});
testWidgets(
'SnackBar has correct end padding when it contains an action with floating behavior',
(WidgetTester tester) async {
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: Builder(
builder: (BuildContext context) {
return GestureDetector(
onTap: () {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: const Text('Some content'),
behavior: SnackBarBehavior.floating,
action: SnackBarAction(label: 'ACTION', onPressed: () {}),
),
);
},
child: const Text('X'),
);
},
),
),
),
);
await tester.tap(find.text('X'));
await tester.pumpAndSettle();
final Offset snackBarTopRight = tester.getTopRight(find.byType(SnackBar));
final Offset actionTopRight = tester.getTopRight(find.byType(SnackBarAction));
expect(
snackBarTopRight.dx - actionTopRight.dx,
8.0 + 15.0,
); // button margin + horizontal scaffold outside margin
},
);
testWidgets(
'Material3 - Floating snackbar with custom width is centered when text direction is rtl',
(WidgetTester tester) async {
// Regression test for https://github.com/flutter/flutter/issues/140125.
const customWidth = 400.0;
await tester.pumpWidget(
MaterialApp(
home: Directionality(
textDirection: TextDirection.rtl,
child: Scaffold(
body: Builder(
builder: (BuildContext context) {
return GestureDetector(
onTap: () {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
behavior: SnackBarBehavior.floating,
width: customWidth,
content: Text('Feeling super snackish'),
),
);
},
child: const Text('X'),
);
},
),
),
),
),
);
await tester.tap(find.text('X'));
await tester.pump(); // Start animation.
await tester.pump(const Duration(milliseconds: 750));
final Finder materialFinder = find.descendant(
of: find.byType(SnackBar),
matching: find.byType(Material),
);
final Offset snackBarBottomLeft = tester.getBottomLeft(materialFinder);
final Offset snackBarBottomRight = tester.getBottomRight(materialFinder);
expect(snackBarBottomLeft.dx, (800 - customWidth) / 2); // Device width is 800.
expect(snackBarBottomRight.dx, (800 + customWidth) / 2); // Device width is 800.
},
);
testWidgets(
'Material2 - Floating snackbar with custom width is centered when text direction is rtl',
(WidgetTester tester) async {
// Regression test for https://github.com/flutter/flutter/issues/147838.
const customWidth = 400.0;
await tester.pumpWidget(
MaterialApp(
theme: ThemeData(useMaterial3: false),
home: Directionality(
textDirection: TextDirection.rtl,
child: Scaffold(
body: Builder(
builder: (BuildContext context) {
return GestureDetector(
onTap: () {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
behavior: SnackBarBehavior.floating,
width: customWidth,
content: Text('Feeling super snackish'),
),
);
},
child: const Text('X'),
);
},
),
),
),
),
);
await tester.tap(find.text('X'));
await tester.pump(); // Start animation.
await tester.pump(const Duration(milliseconds: 750));
final Finder materialFinder = find.descendant(
of: find.byType(SnackBar),
matching: find.byType(Material),
);
final Offset snackBarBottomLeft = tester.getBottomLeft(materialFinder);
final Offset snackBarBottomRight = tester.getBottomRight(materialFinder);
expect(snackBarBottomLeft.dx, (800 - customWidth) / 2); // Device width is 800.
expect(snackBarBottomRight.dx, (800 + customWidth) / 2); // Device width is 800.
},
);
});
testWidgets('SnackBars hero across transitions when using ScaffoldMessenger', (
WidgetTester tester,
) async {
const snackBarText = 'hello snackbar';
const firstHeader = 'home';
const secondHeader = 'second';
const snackTarget = Key('snack-target');
const transitionTarget = Key('transition-target');
Widget buildApp() {
return MaterialApp(
routes: <String, WidgetBuilder>{
'/': (BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text(firstHeader)),
body: Center(
child: ElevatedButton(
key: transitionTarget,
child: const Text('PUSH'),
onPressed: () {
Navigator.of(context).pushNamed('/second');
},
),
),
floatingActionButton: FloatingActionButton(
key: snackTarget,
onPressed: () async {
ScaffoldMessenger.of(
context,
).showSnackBar(const SnackBar(content: Text(snackBarText)));
},
child: const Text('X'),
),
);
},
'/second': (BuildContext context) =>
Scaffold(appBar: AppBar(title: const Text(secondHeader))),
},
);
}
await tester.pumpWidget(buildApp());
expect(find.text(snackBarText), findsNothing);
expect(find.text(firstHeader), findsOneWidget);
expect(find.text(secondHeader), findsNothing);
// Present SnackBar
await tester.tap(find.byKey(snackTarget));
await tester.pump(); // schedule animation
expect(find.text(snackBarText), findsOneWidget);
await tester.pump(); // begin animation
expect(find.text(snackBarText), findsOneWidget);
await tester.pump(const Duration(milliseconds: 750));
expect(find.text(snackBarText), findsOneWidget);
// Push new route
await tester.tap(find.byKey(transitionTarget));
await tester.pump();
expect(find.text(snackBarText), findsOneWidget);
expect(find.text(firstHeader), findsOneWidget);
expect(find.text(secondHeader, skipOffstage: false), findsOneWidget);
await tester.pump();
expect(find.text(snackBarText), findsOneWidget);
expect(find.text(firstHeader), findsOneWidget);
expect(find.text(secondHeader), findsOneWidget);
await tester.pump(const Duration(milliseconds: 1500));
expect(find.text(snackBarText), findsOneWidget);
expect(find.text(firstHeader), findsNothing);
expect(find.text(secondHeader), findsOneWidget);
});
testWidgets('Should have only one SnackBar during back swipe navigation', (
WidgetTester tester,
) async {
const snackBarText = 'hello snackbar';
const snackTarget = Key('snack-target');
const transitionTarget = Key('transition-target');
Widget buildApp() {
final pageTransitionTheme = PageTransitionsTheme(
builders: <TargetPlatform, PageTransitionsBuilder>{
for (final TargetPlatform platform in TargetPlatform.values)
platform: const CupertinoPageTransitionsBuilder(),
},
);
return MaterialApp(
theme: ThemeData(pageTransitionsTheme: pageTransitionTheme),
initialRoute: '/',
routes: <String, WidgetBuilder>{
'/': (BuildContext context) {
return Scaffold(
body: Center(
child: ElevatedButton(
key: transitionTarget,
child: const Text('PUSH'),
onPressed: () {
Navigator.of(context).pushNamed('/second');
},
),
),
);
},
'/second': (BuildContext context) {
return Scaffold(
floatingActionButton: FloatingActionButton(
key: snackTarget,
onPressed: () async {
ScaffoldMessenger.of(
context,
).showSnackBar(const SnackBar(content: Text(snackBarText)));
},
child: const Text('X'),
),
);
},
},
);
}
await tester.pumpWidget(buildApp());
// Transition to second page.
await tester.tap(find.byKey(transitionTarget));
await tester.pumpAndSettle();
// Present SnackBar
await tester.tap(find.byKey(snackTarget));
await tester.pump(); // schedule animation
expect(find.text(snackBarText), findsOneWidget);
await tester.pump(); // begin animation
expect(find.text(snackBarText), findsOneWidget);
await tester.pump(const Duration(milliseconds: 750));
expect(find.text(snackBarText), findsOneWidget);
// Start the gesture at the edge of the screen.
final TestGesture gesture = await tester.startGesture(const Offset(5.0, 200.0));
// Trigger the swipe.
await gesture.moveBy(const Offset(100.0, 0.0));
// Back gestures should trigger and draw the hero transition in the very same
// frame (since the "from" route has already moved to reveal the "to" route).
await tester.pump();
// We should have only one SnackBar displayed on the screen.
expect(find.text(snackBarText), findsOneWidget);
});
testWidgets('Material2 - SnackBars should be shown above the bottomSheet', (
WidgetTester tester,
) async {
await tester.pumpWidget(
MaterialApp(
debugShowCheckedModeBanner: false, // https://github.com/flutter/flutter/issues/143616
theme: ThemeData(useMaterial3: false),
home: const Scaffold(
bottomSheet: SizedBox(width: 200, height: 50, child: ColoredBox(color: Colors.pink)),
),
),
);
final ScaffoldMessengerState scaffoldMessengerState = tester.state(
find.byType(ScaffoldMessenger),
);
scaffoldMessengerState.showSnackBar(
SnackBar(
content: const Text('I love Flutter!'),
duration: const Duration(seconds: 2),
action: SnackBarAction(label: 'ACTION', onPressed: () {}),
behavior: SnackBarBehavior.floating,
),
);
await tester.pumpAndSettle(); // Have the SnackBar fully animate out.
await expectLater(
find.byType(MaterialApp),
matchesGoldenFile('m2_snack_bar.goldenTest.workWithBottomSheet.png'),
);
});
testWidgets('Material3 - SnackBars should be shown above the bottomSheet', (
WidgetTester tester,
) async {
await tester.pumpWidget(
const MaterialApp(
debugShowCheckedModeBanner: false, // https://github.com/flutter/flutter/issues/143616
home: Scaffold(
bottomSheet: SizedBox(width: 200, height: 50, child: ColoredBox(color: Colors.pink)),
),
),
);
final ScaffoldMessengerState scaffoldMessengerState = tester.state(
find.byType(ScaffoldMessenger),
);
scaffoldMessengerState.showSnackBar(
SnackBar(
content: const Text('I love Flutter!'),
duration: const Duration(seconds: 2),
action: SnackBarAction(label: 'ACTION', onPressed: () {}),
behavior: SnackBarBehavior.floating,
),
);
await tester.pumpAndSettle(); // Have the SnackBar fully animate out.
await expectLater(
find.byType(MaterialApp),
matchesGoldenFile('m3_snack_bar.goldenTest.workWithBottomSheet.png'),
);
});
testWidgets('ScaffoldMessenger does not duplicate a SnackBar when presenting a MaterialBanner.', (
WidgetTester tester,
) async {
const materialBannerTapTarget = Key('materialbanner-tap-target');
const snackBarTapTarget = Key('snackbar-tap-target');
const snackBarText = 'SnackBar';
const materialBannerText = 'MaterialBanner';
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: Builder(
builder: (BuildContext context) {
return Column(
children: <Widget>[
GestureDetector(
key: snackBarTapTarget,
onTap: () {
ScaffoldMessenger.of(
context,
).showSnackBar(const SnackBar(content: Text(snackBarText)));
},
behavior: HitTestBehavior.opaque,
child: const SizedBox(height: 100.0, width: 100.0),
),
GestureDetector(
key: materialBannerTapTarget,
onTap: () {
ScaffoldMessenger.of(context).showMaterialBanner(
MaterialBanner(
content: const Text(materialBannerText),
actions: <Widget>[
TextButton(
child: const Text('DISMISS'),
onPressed: () =>
ScaffoldMessenger.of(context).hideCurrentMaterialBanner(),
),
],
),
);
},
behavior: HitTestBehavior.opaque,
child: const SizedBox(height: 100.0, width: 100.0),
),
],
);
},
),
),
),
);
await tester.tap(find.byKey(snackBarTapTarget));
await tester.tap(find.byKey(materialBannerTapTarget));
await tester.pumpAndSettle();
expect(find.text(snackBarText), findsOneWidget);
expect(find.text(materialBannerText), findsOneWidget);
});
testWidgets(
'Material2 - ScaffoldMessenger presents SnackBars to only the root Scaffold when Scaffolds are nested.',
(WidgetTester tester) async {
await tester.pumpWidget(
MaterialApp(
theme: ThemeData(useMaterial3: false),
debugShowCheckedModeBanner: false, // https://github.com/flutter/flutter/issues/143616
home: Scaffold(
body: const Scaffold(),
floatingActionButton: FloatingActionButton(onPressed: () {}),
),
),
);
final ScaffoldMessengerState scaffoldMessengerState = tester.state<ScaffoldMessengerState>(
find.byType(ScaffoldMessenger),
);
scaffoldMessengerState.showSnackBar(
SnackBar(
content: const Text('ScaffoldMessenger'),
duration: const Duration(seconds: 2),
action: SnackBarAction(label: 'ACTION', onPressed: () {}),
behavior: SnackBarBehavior.floating,
),
);
await tester.pumpAndSettle();
expect(find.byType(SnackBar), findsOneWidget);
// The FloatingActionButton helps us identify which Scaffold has the
// SnackBar here. Since the outer Scaffold contains a FAB, the SnackBar
// should be above it. If the inner Scaffold had the SnackBar, it would be
// overlapping the FAB.
await expectLater(
find.byType(MaterialApp),
matchesGoldenFile('m2_snack_bar.scaffold.nested.png'),
);
final Offset snackBarTopRight = tester.getTopRight(find.byType(SnackBar));
expect(snackBarTopRight.dy, 465.0);
},
);
testWidgets(
'Material3 - ScaffoldMessenger presents SnackBars to only the root Scaffold when Scaffolds are nested.',
(WidgetTester tester) async {
await tester.pumpWidget(
MaterialApp(
debugShowCheckedModeBanner: false, // https://github.com/flutter/flutter/issues/143616
home: Scaffold(
body: const Scaffold(),
floatingActionButton: FloatingActionButton(onPressed: () {}),
),
),
);
final ScaffoldMessengerState scaffoldMessengerState = tester.state<ScaffoldMessengerState>(
find.byType(ScaffoldMessenger),
);
scaffoldMessengerState.showSnackBar(
SnackBar(
content: const Text('ScaffoldMessenger'),
duration: const Duration(seconds: 2),
action: SnackBarAction(label: 'ACTION', onPressed: () {}),
behavior: SnackBarBehavior.floating,
),
);
await tester.pumpAndSettle();
expect(find.byType(SnackBar), findsOneWidget);
// The FloatingActionButton helps us identify which Scaffold has the
// SnackBar here. Since the outer Scaffold contains a FAB, the SnackBar
// should be above it. If the inner Scaffold had the SnackBar, it would be
// overlapping the FAB.
await expectLater(
find.byType(MaterialApp),
matchesGoldenFile('m3_snack_bar.scaffold.nested.png'),
);
final Offset snackBarTopRight = tester.getTopRight(find.byType(SnackBar));
expect(snackBarTopRight.dy, 465.0);
},
);
testWidgets('ScaffoldMessengerState clearSnackBars works as expected', (
WidgetTester tester,
) async {
final snackBars = <String>['Hello Snackbar', 'Hi Snackbar', 'Bye Snackbar'];
var snackBarCounter = 0;
const tapTarget = Key('tap-target');
final GlobalKey<ScaffoldMessengerState> scaffoldMessengerKey = GlobalKey();
await tester.pumpWidget(
MaterialApp(
home: ScaffoldMessenger(
key: scaffoldMessengerKey,
child: Scaffold(
body: Builder(
builder: (BuildContext context) {
return GestureDetector(
key: tapTarget,
onTap: () {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(snackBars[snackBarCounter++]),
duration: const Duration(seconds: 2),
),
);
},
behavior: HitTestBehavior.opaque,
child: const SizedBox(height: 100.0, width: 100.0),
);
},
),
),
),
),
);
expect(find.text(snackBars[0]), findsNothing);
expect(find.text(snackBars[1]), findsNothing);
expect(find.text(snackBars[2]), findsNothing);
await tester.tap(find.byKey(tapTarget));
await tester.tap(find.byKey(tapTarget));
await tester.tap(find.byKey(tapTarget));
expect(find.text(snackBars[0]), findsNothing);
expect(find.text(snackBars[1]), findsNothing);
expect(find.text(snackBars[2]), findsNothing);
await tester.pump(); // schedule animation
expect(find.text(snackBars[0]), findsOneWidget);
scaffoldMessengerKey.currentState!.clearSnackBars();
expect(find.text(snackBars[0]), findsOneWidget);
await tester.pump(const Duration(seconds: 2));
expect(find.text(snackBars[0]), findsNothing);
expect(find.text(snackBars[1]), findsNothing);
expect(find.text(snackBars[2]), findsNothing);
});
Widget doBuildApp({
required SnackBarBehavior? behavior,
EdgeInsetsGeometry? margin,
double? width,
double? actionOverflowThreshold,
}) {
return MaterialApp(
home: Scaffold(
floatingActionButton: FloatingActionButton(child: const Icon(Icons.send), onPressed: () {}),
body: Builder(
builder: (BuildContext context) {
return GestureDetector(
onTap: () {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
behavior: behavior,
margin: margin,
width: width,
content: const Text('I am a snack bar.'),
duration: const Duration(seconds: 2),
action: SnackBarAction(label: 'ACTION', onPressed: () {}),
actionOverflowThreshold: actionOverflowThreshold,
),
);
},
child: const Text('X'),
);
},
),
),
);
}
testWidgets('Setting SnackBarBehavior.fixed will still assert for margin', (
WidgetTester tester,
) async {
// Regression test for https://github.com/flutter/flutter/issues/84935
await tester.pumpWidget(
doBuildApp(behavior: SnackBarBehavior.fixed, margin: const EdgeInsets.all(8.0)),
);
await tester.tap(find.text('X'));
await tester.pump(); // start animation
await tester.pump(const Duration(milliseconds: 750));
final exception = tester.takeException() as AssertionError;
expect(
exception.message,
'Margin can only be used with floating behavior. SnackBarBehavior.fixed '
'was set in the SnackBar constructor.',
);
});
testWidgets('Default SnackBarBehavior will still assert for margin', (WidgetTester tester) async {
// Regression test for https://github.com/flutter/flutter/issues/84935
await tester.pumpWidget(doBuildApp(behavior: null, margin: const EdgeInsets.all(8.0)));
await tester.tap(find.text('X'));
await tester.pump(); // start animation
await tester.pump(const Duration(milliseconds: 750));
final exception = tester.takeException() as AssertionError;
expect(
exception.message,
'Margin can only be used with floating behavior. SnackBarBehavior.fixed '
'was set by default.',
);
});
testWidgets('Setting SnackBarBehavior.fixed will still assert for width', (
WidgetTester tester,
) async {
// Regression test for https://github.com/flutter/flutter/issues/84935
await tester.pumpWidget(doBuildApp(behavior: SnackBarBehavior.fixed, width: 5.0));
await tester.tap(find.text('X'));
await tester.pump(); // start animation
await tester.pump(const Duration(milliseconds: 750));
final exception = tester.takeException() as AssertionError;
expect(
exception.message,
'Width can only be used with floating behavior. SnackBarBehavior.fixed '
'was set in the SnackBar constructor.',
);
});
testWidgets('Default SnackBarBehavior will still assert for width', (WidgetTester tester) async {
// Regression test for https://github.com/flutter/flutter/issues/84935
await tester.pumpWidget(doBuildApp(behavior: null, width: 5.0));
await tester.tap(find.text('X'));
await tester.pump(); // start animation
await tester.pump(const Duration(milliseconds: 750));
final exception = tester.takeException() as AssertionError;
expect(
exception.message,
'Width can only be used with floating behavior. SnackBarBehavior.fixed '
'was set by default.',
);
});
for (final overflowThreshold in <double>[-1.0, -.0001, 1.000001, 5]) {
testWidgets('SnackBar will assert for actionOverflowThreshold outside of 0-1 range', (
WidgetTester tester,
) async {
await tester.pumpWidget(
doBuildApp(actionOverflowThreshold: overflowThreshold, behavior: SnackBarBehavior.fixed),
);
await tester.tap(find.text('X'));
await tester.pump(); // start animation
await tester.pump(const Duration(milliseconds: 750));
final exception = tester.takeException() as AssertionError;
expect(exception.message, 'Action overflow threshold must be between 0 and 1 inclusive');
});
}
testWidgets('Material2 - Snackbar by default clips BackdropFilter', (WidgetTester tester) async {
// Regression test for https://github.com/flutter/flutter/issues/98205
await tester.pumpWidget(
MaterialApp(
theme: ThemeData(useMaterial3: false),
debugShowCheckedModeBanner: false, // https://github.com/flutter/flutter/issues/143616
home: Scaffold(
body: const Scaffold(),
floatingActionButton: FloatingActionButton(onPressed: () {}),
),
),
);
final ScaffoldMessengerState scaffoldMessengerState = tester.state<ScaffoldMessengerState>(
find.byType(ScaffoldMessenger),
);
scaffoldMessengerState.showSnackBar(
SnackBar(
backgroundColor: Colors.transparent,
content: BackdropFilter(
filter: ImageFilter.blur(sigmaX: 20.0, sigmaY: 20.0),
child: const Text('I am a snack bar.'),
),
duration: const Duration(seconds: 2),
action: SnackBarAction(label: 'ACTION', onPressed: () {}),
behavior: SnackBarBehavior.fixed,
),
);
await tester.pumpAndSettle();
await tester.tap(find.text('I am a snack bar.'));
await tester.pump(); // start animation
await tester.pump(const Duration(milliseconds: 750));
await expectLater(
find.byType(MaterialApp),
matchesGoldenFile('m2_snack_bar.goldenTest.backdropFilter.png'),
);
});
testWidgets('Material3 - Snackbar by default clips BackdropFilter', (WidgetTester tester) async {
// Regression test for https://github.com/flutter/flutter/issues/98205
await tester.pumpWidget(
MaterialApp(
debugShowCheckedModeBanner: false, // https://github.com/flutter/flutter/issues/143616
home: Scaffold(
body: const Scaffold(),
floatingActionButton: FloatingActionButton(onPressed: () {}),
),
),
);
final ScaffoldMessengerState scaffoldMessengerState = tester.state<ScaffoldMessengerState>(
find.byType(ScaffoldMessenger),
);
scaffoldMessengerState.showSnackBar(
SnackBar(
backgroundColor: Colors.transparent,
content: BackdropFilter(
filter: ImageFilter.blur(sigmaX: 20.0, sigmaY: 20.0),
child: const Text('I am a snack bar.'),
),
duration: const Duration(seconds: 2),
action: SnackBarAction(label: 'ACTION', onPressed: () {}),
behavior: SnackBarBehavior.fixed,
),
);
await tester.pumpAndSettle();
await tester.tap(find.text('I am a snack bar.'));
await tester.pump(); // start animation
await tester.pump(const Duration(milliseconds: 750));
await expectLater(
find.byType(MaterialApp),
matchesGoldenFile('m3_snack_bar.goldenTest.backdropFilter.png'),
);
});
testWidgets('Floating snackbar can display optional icon', (WidgetTester tester) async {
await tester.pumpWidget(
MaterialApp(
theme: ThemeData(useMaterial3: false),
debugShowCheckedModeBanner: false, // https://github.com/flutter/flutter/issues/143616
home: const Scaffold(
bottomSheet: SizedBox(width: 200, height: 50, child: ColoredBox(color: Colors.pink)),
),
),
);
final ScaffoldMessengerState scaffoldMessengerState = tester.state(
find.byType(ScaffoldMessenger),
);
scaffoldMessengerState.showSnackBar(
SnackBar(
content: const Text('Feeling snackish'),
duration: const Duration(seconds: 2),
action: SnackBarAction(label: 'ACTION', onPressed: () {}),
behavior: SnackBarBehavior.floating,
showCloseIcon: true,
),
);
await tester.pumpAndSettle(); // Have the SnackBar fully animate out.
await expectLater(
find.byType(MaterialApp),
matchesGoldenFile('snack_bar.goldenTest.floatingWithActionWithIcon.png'),
);
});
testWidgets('SnackBar has tooltip for Close Button', (WidgetTester tester) async {
// Regression test for https://github.com/flutter/flutter/issues/143793
await tester.pumpWidget(
MaterialApp(
theme: ThemeData(useMaterial3: false),
debugShowCheckedModeBanner: false, // https://github.com/flutter/flutter/issues/143616
home: const Scaffold(
bottomSheet: SizedBox(width: 200, height: 50, child: ColoredBox(color: Colors.pink)),
),
),
);
final ScaffoldMessengerState scaffoldMessengerState = tester.state(
find.byType(ScaffoldMessenger),
);
scaffoldMessengerState.showSnackBar(
SnackBar(
content: const Text('Snackbar with close button'),
duration: const Duration(days: 365),
action: SnackBarAction(label: 'ACTION', onPressed: () {}),
behavior: SnackBarBehavior.floating,
showCloseIcon: true,
),
);
await tester.pumpAndSettle(); // Have the SnackBar fully animate in.
expect(
find.byTooltip(MaterialLocalizations.of(scaffoldMessengerState.context).closeButtonLabel),
findsOneWidget,
);
});
testWidgets('Material2 - Fixed width snackbar can display optional icon', (
WidgetTester tester,
) async {
await tester.pumpWidget(
MaterialApp(
theme: ThemeData(useMaterial3: false),
debugShowCheckedModeBanner: false, // https://github.com/flutter/flutter/issues/143616
home: const Scaffold(
bottomSheet: SizedBox(width: 200, height: 50, child: ColoredBox(color: Colors.pink)),
),
),
);
final ScaffoldMessengerState scaffoldMessengerState = tester.state(
find.byType(ScaffoldMessenger),
);
scaffoldMessengerState.showSnackBar(
SnackBar(
content: const Text('Go get a snack'),
duration: const Duration(seconds: 2),
action: SnackBarAction(label: 'ACTION', onPressed: () {}),
showCloseIcon: true,
behavior: SnackBarBehavior.fixed,
),
);
await tester.pumpAndSettle(); // Have the SnackBar fully animate out.
await expectLater(
find.byType(MaterialApp),
matchesGoldenFile('m2_snack_bar.goldenTest.fixedWithActionWithIcon.png'),
);
});
testWidgets('Material3 - Fixed width snackbar can display optional icon', (
WidgetTester tester,
) async {
await tester.pumpWidget(
const MaterialApp(
debugShowCheckedModeBanner: false, // https://github.com/flutter/flutter/issues/143616
home: Scaffold(
bottomSheet: SizedBox(width: 200, height: 50, child: ColoredBox(color: Colors.pink)),
),
),
);
final ScaffoldMessengerState scaffoldMessengerState = tester.state(
find.byType(ScaffoldMessenger),
);
scaffoldMessengerState.showSnackBar(
SnackBar(
content: const Text('Go get a snack'),
duration: const Duration(seconds: 2),
action: SnackBarAction(label: 'ACTION', onPressed: () {}),
showCloseIcon: true,
behavior: SnackBarBehavior.fixed,
),
);
await tester.pumpAndSettle(); // Have the SnackBar fully animate out.
await expectLater(
find.byType(MaterialApp),
matchesGoldenFile('m3_snack_bar.goldenTest.fixedWithActionWithIcon.png'),
);
});
testWidgets('Material2 - Fixed snackbar can display optional icon without action', (
WidgetTester tester,
) async {
await tester.pumpWidget(
MaterialApp(
theme: ThemeData(useMaterial3: false),
debugShowCheckedModeBanner: false, // https://github.com/flutter/flutter/issues/143616
home: const Scaffold(
bottomSheet: SizedBox(width: 200, height: 50, child: ColoredBox(color: Colors.pink)),
),
),
);
final ScaffoldMessengerState scaffoldMessengerState = tester.state(
find.byType(ScaffoldMessenger),
);
scaffoldMessengerState.showSnackBar(
const SnackBar(
content: Text('I wonder if there are snacks nearby?'),
duration: Duration(seconds: 2),
behavior: SnackBarBehavior.fixed,
showCloseIcon: true,
),
);
await tester.pumpAndSettle(); // Have the SnackBar fully animate out.
await expectLater(
find.byType(MaterialApp),
matchesGoldenFile('m2_snack_bar.goldenTest.fixedWithIcon.png'),
);
});
testWidgets('Material3 - Fixed snackbar can display optional icon without action', (
WidgetTester tester,
) async {
await tester.pumpWidget(
const MaterialApp(
debugShowCheckedModeBanner: false, // https://github.com/flutter/flutter/issues/143616
home: Scaffold(
bottomSheet: SizedBox(width: 200, height: 50, child: ColoredBox(color: Colors.pink)),
),
),
);
final ScaffoldMessengerState scaffoldMessengerState = tester.state(
find.byType(ScaffoldMessenger),
);
scaffoldMessengerState.showSnackBar(
const SnackBar(
content: Text('I wonder if there are snacks nearby?'),
duration: Duration(seconds: 2),
behavior: SnackBarBehavior.fixed,
showCloseIcon: true,
),
);
await tester.pumpAndSettle(); // Have the SnackBar fully animate out.
await expectLater(
find.byType(MaterialApp),
matchesGoldenFile('m3_snack_bar.goldenTest.fixedWithIcon.png'),
);
});
testWidgets('Material2 - Floating width snackbar can display optional icon without action', (
WidgetTester tester,
) async {
await tester.pumpWidget(
MaterialApp(
theme: ThemeData(useMaterial3: false),
debugShowCheckedModeBanner: false, // https://github.com/flutter/flutter/issues/143616
home: const Scaffold(
bottomSheet: SizedBox(width: 200, height: 50, child: ColoredBox(color: Colors.pink)),
),
),
);
final ScaffoldMessengerState scaffoldMessengerState = tester.state(
find.byType(ScaffoldMessenger),
);
scaffoldMessengerState.showSnackBar(
const SnackBar(
content: Text('Must go get a snack!'),
duration: Duration(seconds: 2),
showCloseIcon: true,
behavior: SnackBarBehavior.floating,
),
);
await tester.pumpAndSettle(); // Have the SnackBar fully animate out.
await expectLater(
find.byType(MaterialApp),
matchesGoldenFile('m2_snack_bar.goldenTest.floatingWithIcon.png'),
);
});
testWidgets('Material3 - Floating width snackbar can display optional icon without action', (
WidgetTester tester,
) async {
await tester.pumpWidget(
const MaterialApp(
debugShowCheckedModeBanner: false, // https://github.com/flutter/flutter/issues/143616
home: Scaffold(
bottomSheet: SizedBox(width: 200, height: 50, child: ColoredBox(color: Colors.pink)),
),
),
);
final ScaffoldMessengerState scaffoldMessengerState = tester.state(
find.byType(ScaffoldMessenger),
);
scaffoldMessengerState.showSnackBar(
const SnackBar(
content: Text('Must go get a snack!'),
duration: Duration(seconds: 2),
showCloseIcon: true,
behavior: SnackBarBehavior.floating,
),
);
await tester.pumpAndSettle(); // Have the SnackBar fully animate out.
await expectLater(
find.byType(MaterialApp),
matchesGoldenFile('m3_snack_bar.goldenTest.floatingWithIcon.png'),
);
});
testWidgets('Material2 - Floating multi-line snackbar with icon is aligned correctly', (
WidgetTester tester,
) async {
await tester.pumpWidget(
MaterialApp(
theme: ThemeData(useMaterial3: false),
debugShowCheckedModeBanner: false, // https://github.com/flutter/flutter/issues/143616
home: const Scaffold(
bottomSheet: SizedBox(width: 200, height: 50, child: ColoredBox(color: Colors.pink)),
),
),
);
final ScaffoldMessengerState scaffoldMessengerState = tester.state(
find.byType(ScaffoldMessenger),
);
scaffoldMessengerState.showSnackBar(
const SnackBar(
content: Text(
'This is a really long snackbar message. So long, it spans across more than one line!',
),
duration: Duration(seconds: 2),
showCloseIcon: true,
behavior: SnackBarBehavior.floating,
),
);
await tester.pumpAndSettle(); // Have the SnackBar fully animate out.
await expectLater(
find.byType(MaterialApp),
matchesGoldenFile('m2_snack_bar.goldenTest.multiLineWithIcon.png'),
);
});
testWidgets('Material3 - Floating multi-line snackbar with icon is aligned correctly', (
WidgetTester tester,
) async {
await tester.pumpWidget(
const MaterialApp(
debugShowCheckedModeBanner: false, // https://github.com/flutter/flutter/issues/143616
home: Scaffold(
bottomSheet: SizedBox(width: 200, height: 50, child: ColoredBox(color: Colors.pink)),
),
),
);
final ScaffoldMessengerState scaffoldMessengerState = tester.state(
find.byType(ScaffoldMessenger),
);
scaffoldMessengerState.showSnackBar(
const SnackBar(
content: Text(
'This is a really long snackbar message. So long, it spans across more than one line!',
),
duration: Duration(seconds: 2),
showCloseIcon: true,
behavior: SnackBarBehavior.floating,
),
);
await tester.pumpAndSettle(); // Have the SnackBar fully animate out.
await expectLater(
find.byType(MaterialApp),
matchesGoldenFile('m3_snack_bar.goldenTest.multiLineWithIcon.png'),
);
});
testWidgets(
'Material2 - Floating multi-line snackbar with icon and actionOverflowThreshold=1 is aligned correctly',
(WidgetTester tester) async {
await tester.pumpWidget(
MaterialApp(
theme: ThemeData(useMaterial3: false),
debugShowCheckedModeBanner: false, // https://github.com/flutter/flutter/issues/143616
home: const Scaffold(
bottomSheet: SizedBox(width: 200, height: 50, child: ColoredBox(color: Colors.pink)),
),
),
);
final ScaffoldMessengerState scaffoldMessengerState = tester.state(
find.byType(ScaffoldMessenger),
);
scaffoldMessengerState.showSnackBar(
const SnackBar(
content: Text(
'This is a really long snackbar message. So long, it spans across more than one line!',
),
duration: Duration(seconds: 2),
showCloseIcon: true,
behavior: SnackBarBehavior.floating,
actionOverflowThreshold: 1,
),
);
await tester.pumpAndSettle(); // Have the SnackBar fully animate in.
await expectLater(
find.byType(MaterialApp),
matchesGoldenFile(
'm2_snack_bar.goldenTest.multiLineWithIconWithZeroActionOverflowThreshold.png',
),
);
},
);
testWidgets(
'Material3 - Floating multi-line snackbar with icon and actionOverflowThreshold=1 is aligned correctly',
(WidgetTester tester) async {
await tester.pumpWidget(
const MaterialApp(
debugShowCheckedModeBanner: false, // https://github.com/flutter/flutter/issues/143616
home: Scaffold(
bottomSheet: SizedBox(width: 200, height: 50, child: ColoredBox(color: Colors.pink)),
),
),
);
final ScaffoldMessengerState scaffoldMessengerState = tester.state(
find.byType(ScaffoldMessenger),
);
scaffoldMessengerState.showSnackBar(
const SnackBar(
content: Text(
'This is a really long snackbar message. So long, it spans across more than one line!',
),
duration: Duration(seconds: 2),
showCloseIcon: true,
behavior: SnackBarBehavior.floating,
actionOverflowThreshold: 1,
),
);
await tester.pumpAndSettle(); // Have the SnackBar fully animate in.
await expectLater(
find.byType(MaterialApp),
matchesGoldenFile(
'm3_snack_bar.goldenTest.multiLineWithIconWithZeroActionOverflowThreshold.png',
),
);
},
);
testWidgets('ScaffoldMessenger will alert for snackbars that cannot be presented', (
WidgetTester tester,
) async {
// Regression test for https://github.com/flutter/flutter/issues/103004
await tester.pumpWidget(const MaterialApp(home: Center()));
final ScaffoldMessengerState scaffoldMessengerState = tester.state<ScaffoldMessengerState>(
find.byType(ScaffoldMessenger),
);
expect(
() {
scaffoldMessengerState.showSnackBar(const SnackBar(content: Text('SnackBar')));
},
throwsA(
isA<AssertionError>().having(
(AssertionError error) => error.toString(),
'description',
contains(
'ScaffoldMessenger.showSnackBar was called, but there are currently '
'no descendant Scaffolds to present to.',
),
),
),
);
});
testWidgets('SnackBarAction backgroundColor works as a Color', (WidgetTester tester) async {
const Color backgroundColor = Colors.blue;
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: Builder(
builder: (BuildContext context) {
return GestureDetector(
onTap: () {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: const Text('I am a snack bar.'),
duration: const Duration(seconds: 2),
action: SnackBarAction(
backgroundColor: backgroundColor,
label: 'ACTION',
onPressed: () {},
),
),
);
},
child: const Text('Tap'),
);
},
),
),
),
);
await tester.tap(find.text('Tap'));
await tester.pumpAndSettle();
final Material materialBeforeDismissed = tester.widget<Material>(
find.descendant(
of: find.widgetWithText(TextButton, 'ACTION'),
matching: find.byType(Material),
),
);
expect(materialBeforeDismissed.color, backgroundColor);
await tester.tap(find.text('ACTION'));
await tester.pump();
final Material materialAfterDismissed = tester.widget<Material>(
find.descendant(
of: find.widgetWithText(TextButton, 'ACTION'),
matching: find.byType(Material),
),
);
expect(materialAfterDismissed.color, Colors.transparent);
});
testWidgets('SnackBarAction backgroundColor works as a WidgetStateColor', (
WidgetTester tester,
) async {
final backgroundColor = WidgetStateColor.resolveWith((Set<WidgetState> states) {
if (states.contains(WidgetState.disabled)) {
return Colors.blue;
}
return Colors.purple;
});
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: Builder(
builder: (BuildContext context) {
return GestureDetector(
onTap: () {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: const Text('I am a snack bar.'),
duration: const Duration(seconds: 2),
action: SnackBarAction(
backgroundColor: backgroundColor,
label: 'ACTION',
onPressed: () {},
),
),
);
},
child: const Text('Tap'),
);
},
),
),
),
);
await tester.tap(find.text('Tap'));
await tester.pumpAndSettle();
final Material materialBeforeDismissed = tester.widget<Material>(
find.descendant(
of: find.widgetWithText(TextButton, 'ACTION'),
matching: find.byType(Material),
),
);
expect(materialBeforeDismissed.color, Colors.purple);
await tester.tap(find.text('ACTION'));
await tester.pump();
final Material materialAfterDismissed = tester.widget<Material>(
find.descendant(
of: find.widgetWithText(TextButton, 'ACTION'),
matching: find.byType(Material),
),
);
expect(materialAfterDismissed.color, Colors.blue);
});
testWidgets('SnackBarAction disabledBackgroundColor works as expected', (
WidgetTester tester,
) async {
const Color backgroundColor = Colors.blue;
const Color disabledBackgroundColor = Colors.red;
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: Builder(
builder: (BuildContext context) {
return GestureDetector(
onTap: () {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: const Text('I am a snack bar.'),
duration: const Duration(seconds: 2),
action: SnackBarAction(
backgroundColor: backgroundColor,
disabledBackgroundColor: disabledBackgroundColor,
label: 'ACTION',
onPressed: () {},
),
),
);
},
child: const Text('Tap'),
);
},
),
),
),
);
await tester.tap(find.text('Tap'));
await tester.pumpAndSettle();
final Material materialBeforeDismissed = tester.widget<Material>(
find.descendant(
of: find.widgetWithText(TextButton, 'ACTION'),
matching: find.byType(Material),
),
);
expect(materialBeforeDismissed.color, backgroundColor);
await tester.tap(find.text('ACTION'));
await tester.pump();
final Material materialAfterDismissed = tester.widget<Material>(
find.descendant(
of: find.widgetWithText(TextButton, 'ACTION'),
matching: find.byType(Material),
),
);
expect(materialAfterDismissed.color, disabledBackgroundColor);
});
testWidgets(
'SnackBarAction asserts when backgroundColor is a WidgetStateColor and disabledBackgroundColor is also provided',
(WidgetTester tester) async {
final Color backgroundColor = WidgetStateColor.resolveWith((Set<WidgetState> states) {
if (states.contains(WidgetState.disabled)) {
return Colors.blue;
}
return Colors.purple;
});
const Color disabledBackgroundColor = Colors.red;
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: Builder(
builder: (BuildContext context) {
return GestureDetector(
onTap: () {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: const Text('I am a snack bar.'),
duration: const Duration(seconds: 2),
action: SnackBarAction(
backgroundColor: backgroundColor,
disabledBackgroundColor: disabledBackgroundColor,
label: 'ACTION',
onPressed: () {},
),
),
);
},
child: const Text('Tap'),
);
},
),
),
),
);
await tester.tap(find.text('Tap'));
await tester.pumpAndSettle();
expect(
tester.takeException(),
isAssertionError.having(
(AssertionError e) => e.toString(),
'description',
contains(
'disabledBackgroundColor must not be provided when background color is a WidgetStateColor',
),
),
);
},
);
testWidgets('SnackBar material applies SnackBar.clipBehavior', (WidgetTester tester) async {
await tester.pumpWidget(MaterialApp(home: Scaffold(body: Container())));
final ScaffoldMessengerState scaffoldMessengerState = tester.state(
find.byType(ScaffoldMessenger),
);
scaffoldMessengerState.showSnackBar(const SnackBar(content: Text('I am a snack bar.')));
await tester.pumpAndSettle(); // Have the SnackBar fully animate out.
Material material = tester.widget<Material>(
find.descendant(of: find.byType(SnackBar), matching: find.byType(Material)),
);
expect(material.clipBehavior, Clip.hardEdge);
scaffoldMessengerState.hideCurrentSnackBar(); // Hide the SnackBar.
await tester.pumpAndSettle(); // Have the SnackBar fully animate out.
scaffoldMessengerState.showSnackBar(
const SnackBar(content: Text('I am a snack bar.'), clipBehavior: Clip.antiAlias),
);
await tester.pumpAndSettle(); // Have the SnackBar fully animate in.
material = tester.widget<Material>(
find.descendant(of: find.byType(SnackBar), matching: find.byType(Material)),
);
expect(material.clipBehavior, Clip.antiAlias);
});
testWidgets('Tap on button behind snack bar defined by width', (WidgetTester tester) async {
tester.view.physicalSize = const Size.square(200);
tester.view.devicePixelRatio = 1;
addTearDown(tester.view.resetPhysicalSize);
addTearDown(tester.view.resetDevicePixelRatio);
const buttonText = 'Show snackbar';
const snackbarContent = 'Snackbar';
const buttonText2 = 'Try press me';
final completer = Completer<void>();
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: Builder(
builder: (BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
ElevatedButton(
onPressed: () {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
behavior: SnackBarBehavior.floating,
width: 100,
content: Text(snackbarContent),
),
);
},
child: const Text(buttonText),
),
ElevatedButton(
onPressed: () {
completer.complete();
},
child: const Text(buttonText2),
),
],
);
},
),
),
),
);
await tester.tap(find.text(buttonText));
await tester.pumpAndSettle();
expect(find.text(snackbarContent), findsOneWidget);
await tester.tapAt(tester.getTopLeft(find.text(buttonText2)));
expect(find.text(snackbarContent), findsOneWidget);
expect(completer.isCompleted, true);
});
testWidgets('Tap on button behind snack bar defined by margin', (WidgetTester tester) async {
// Regression test for https://github.com/flutter/flutter/issues/78537.
tester.view.physicalSize = const Size.square(200);
tester.view.devicePixelRatio = 1;
addTearDown(tester.view.resetPhysicalSize);
addTearDown(tester.view.resetDevicePixelRatio);
const buttonText = 'Show snackbar';
const snackbarContent = 'Snackbar';
const buttonText2 = 'Try press me';
final completer = Completer<void>();
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: Builder(
builder: (BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
ElevatedButton(
onPressed: () {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
behavior: SnackBarBehavior.floating,
margin: EdgeInsets.only(left: 100),
content: Text(snackbarContent),
),
);
},
child: const Text(buttonText),
),
ElevatedButton(
onPressed: () {
completer.complete();
},
child: const Text(buttonText2),
),
],
);
},
),
),
),
);
await tester.tap(find.text(buttonText));
await tester.pumpAndSettle();
expect(find.text(snackbarContent), findsOneWidget);
await tester.tapAt(tester.getTopLeft(find.text(buttonText2)));
expect(find.text(snackbarContent), findsOneWidget);
expect(completer.isCompleted, true);
});
testWidgets("Can't tap on button behind snack bar defined by margin and HitTestBehavior.opaque", (
WidgetTester tester,
) async {
// Regression test for https://github.com/flutter/flutter/issues/78537.
tester.view.physicalSize = const Size.square(200);
tester.view.devicePixelRatio = 1;
addTearDown(tester.view.resetPhysicalSize);
addTearDown(tester.view.resetDevicePixelRatio);
const buttonText = 'Show snackbar';
const snackbarContent = 'Snackbar';
const buttonText2 = 'Try press me';
final completer = Completer<void>();
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: Builder(
builder: (BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
ElevatedButton(
onPressed: () {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
hitTestBehavior: HitTestBehavior.opaque,
behavior: SnackBarBehavior.floating,
margin: EdgeInsets.only(left: 100),
content: Text(snackbarContent),
),
);
},
child: const Text(buttonText),
),
ElevatedButton(
onPressed: () {
completer.complete();
},
child: const Text(buttonText2),
),
],
);
},
),
),
),
);
await tester.tap(find.text(buttonText));
await tester.pumpAndSettle();
expect(find.text(snackbarContent), findsOneWidget);
await tester.tapAt(tester.getTopLeft(find.text(buttonText2)));
expect(find.text(snackbarContent), findsOneWidget);
expect(completer.isCompleted, false);
});
testWidgets('Action text button uses correct overlay color', (WidgetTester tester) async {
final theme = ThemeData();
await tester.pumpWidget(
MaterialApp(
theme: theme,
home: Scaffold(
body: Builder(
builder: (BuildContext context) {
return GestureDetector(
onTap: () {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: const Text('I am a snack bar.'),
action: SnackBarAction(label: 'ACTION', onPressed: () {}),
),
);
},
child: const Text('X'),
);
},
),
),
),
);
await tester.tap(find.text('X'));
await tester.pumpAndSettle();
final ButtonStyle? actionButtonStyle = tester
.widget<TextButton>(find.widgetWithText(TextButton, 'ACTION'))
.style;
expect(
actionButtonStyle?.overlayColor?.resolve(<WidgetState>{WidgetState.hovered}),
theme.colorScheme.inversePrimary.withOpacity(0.08),
);
});
testWidgets(
'Can interact with widgets behind SnackBar when insetPadding is set in SnackBarThemeData',
(WidgetTester tester) async {
// Regression test for https://github.com/flutter/flutter/issues/148566.
tester.view.physicalSize = const Size.square(200);
tester.view.devicePixelRatio = 1;
addTearDown(tester.view.resetPhysicalSize);
addTearDown(tester.view.resetDevicePixelRatio);
const buttonText = 'Show snackbar';
const snackbarContent = 'Snackbar';
const buttonText2 = 'Try press me';
final completer = Completer<void>();
await tester.pumpWidget(
MaterialApp(
theme: ThemeData(
snackBarTheme: const SnackBarThemeData(insetPadding: EdgeInsets.only(left: 100)),
),
home: Scaffold(
body: Builder(
builder: (BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
ElevatedButton(
onPressed: () {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
behavior: SnackBarBehavior.floating,
content: Text(snackbarContent),
),
);
},
child: const Text(buttonText),
),
ElevatedButton(
onPressed: () {
completer.complete();
},
child: const Text(buttonText2),
),
],
);
},
),
),
),
);
await tester.tap(find.text(buttonText));
await tester.pumpAndSettle();
expect(find.text(snackbarContent), findsOneWidget);
await tester.tapAt(tester.getTopLeft(find.text(buttonText2)));
expect(find.text(snackbarContent), findsOneWidget);
expect(completer.isCompleted, true);
},
);
testWidgets('Setting persist to true prevents timeout', (WidgetTester tester) async {
const buttonText = 'Show snackbar';
const snackbarContent = 'Snackbar';
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: Builder(
builder: (BuildContext context) {
return ElevatedButton(
onPressed: () {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
duration: Duration(seconds: 1),
persist: true,
showCloseIcon: true,
content: Text(snackbarContent),
),
);
},
child: const Text(buttonText),
);
},
),
),
),
);
await tester.tap(find.text(buttonText));
await tester.pump(const Duration(milliseconds: 750));
// The snackbar shows up before the timeout.
expect(find.text(snackbarContent), findsOneWidget);
await tester.pump(const Duration(milliseconds: 1500));
await tester.pumpAndSettle();
// The snackbar is still there after the timeout.
expect(find.text(snackbarContent), findsOneWidget);
await tester.tap(find.byIcon(Icons.close));
await tester.pumpAndSettle();
// The snackbar is dismissed.
expect(find.text(snackbarContent), findsNothing);
});
testWidgets('Setting persist to false so snackbar auto dismisses', (WidgetTester tester) async {
const buttonText = 'Show';
const snackbarContent = 'SnackbarContent';
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: Builder(
builder: (BuildContext context) {
return ElevatedButton(
onPressed: () {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
duration: Duration(seconds: 1),
persist: false,
showCloseIcon: true,
content: Text(snackbarContent),
),
);
},
child: const Text(buttonText),
);
},
),
),
),
);
await tester.tap(find.byType(ElevatedButton));
await tester.pump();
await tester.pump(const Duration(milliseconds: 750));
// The snackbar shows up before the timeout.
expect(find.text(snackbarContent), findsOneWidget);
await tester.pump(const Duration(milliseconds: 1500));
await tester.pumpAndSettle();
// The snackbar auto dismisses after the timeout.
expect(find.text(snackbarContent), findsNothing);
});
testWidgets('Setting persist to false overrides accessibleNavigation', (
WidgetTester tester,
) async {
const buttonText = 'Show';
const snackbarContent = 'SnackbarContent';
await tester.pumpWidget(
MediaQuery(
data: const MediaQueryData(accessibleNavigation: true),
child: MaterialApp(
home: Scaffold(
body: Builder(
builder: (BuildContext context) {
return ElevatedButton(
onPressed: () {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
duration: const Duration(seconds: 1),
persist: false,
action: SnackBarAction(label: 'Action', onPressed: () {}),
content: const Text(snackbarContent),
),
);
},
child: const Text(buttonText),
);
},
),
),
),
),
);
await tester.tap(find.byType(ElevatedButton));
await tester.pumpAndSettle();
expect(find.text(snackbarContent), findsOneWidget);
await tester.pump(const Duration(seconds: 10));
await tester.pumpAndSettle();
// The snackbar auto dismisses after the timeout.
expect(find.text(snackbarContent), findsNothing);
});
testWidgets('SnackBarAction does not crash at zero area', (WidgetTester tester) async {
await tester.pumpWidget(
MaterialApp(
home: Center(
child: SizedBox.shrink(
child: SnackBarAction(label: 'X', onPressed: () {}),
),
),
),
);
expect(tester.getSize(find.byType(SnackBarAction)), Size.zero);
});
}
/// Start test for "SnackBar dismiss test".
Future<void> _testSnackBarDismiss({
required WidgetTester tester,
required Key tapTarget,
required double scaffoldWidth,
required ValueChanged<DismissDirection> onDismissDirectionChange,
required VoidCallback onDragGestureChange,
}) async {
final Map<DismissDirection, List<Offset>> dragGestures = _getDragGesturesOfDismissDirections(
scaffoldWidth,
);
for (final DismissDirection key in dragGestures.keys) {
onDismissDirectionChange(key);
for (final Offset dragGesture in dragGestures[key]!) {
onDragGestureChange();
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'), dragGesture);
await tester.pump(); // bar1 dismissed, bar2 begins animating
expect(find.text('bar1'), findsNothing);
expect(find.text('bar2'), findsOneWidget);
await tester.pump(
const Duration(milliseconds: 750),
); // 0.75s // animation last frame; two second timer starts here
await tester.drag(find.text('bar2'), dragGesture);
await tester.pump(); // bar2 dismissed
expect(find.text('bar1'), findsNothing);
expect(find.text('bar2'), findsNothing);
}
}
}
/// Create drag gestures for DismissDirections.
Map<DismissDirection, List<Offset>> _getDragGesturesOfDismissDirections(double scaffoldWidth) {
final dragGestures = <DismissDirection, List<Offset>>{};
for (final DismissDirection val in DismissDirection.values) {
switch (val) {
case DismissDirection.down:
dragGestures[val] = <Offset>[const Offset(0.0, 50.0)]; // drag to bottom gesture
case DismissDirection.up:
dragGestures[val] = <Offset>[const Offset(0.0, -50.0)]; // drag to top gesture
case DismissDirection.vertical:
dragGestures[val] = <Offset>[
const Offset(0.0, 50.0), // drag to bottom gesture
const Offset(0.0, -50.0), // drag to top gesture
];
case DismissDirection.startToEnd:
dragGestures[val] = <Offset>[Offset(scaffoldWidth, 0.0)]; // drag to right gesture
case DismissDirection.endToStart:
dragGestures[val] = <Offset>[Offset(-scaffoldWidth, 0.0)]; // drag to left gesture
case DismissDirection.horizontal:
dragGestures[val] = <Offset>[
Offset(scaffoldWidth, 0.0), // drag to right gesture
Offset(-scaffoldWidth, 0.0), // drag to left gesture
];
case DismissDirection.none:
break;
}
}
return dragGestures;
}
class _TestMaterialStateColor extends WidgetStateColor {
const _TestMaterialStateColor() : super(_colorRed);
static const int _colorRed = 0xFFF44336;
static const int _colorBlue = 0xFF2196F3;
@override
Color resolve(Set<WidgetState> states) {
if (states.contains(WidgetState.pressed)) {
return const Color(_colorBlue);
}
return const Color(_colorRed);
}
}
class _CustomFloatingActionButtonLocation extends StandardFabLocation
with FabEndOffsetX, FabFloatOffsetY {
const _CustomFloatingActionButtonLocation();
}