blob: 9f3f803cd4c1f7a9aa59cd7f6c15e2e04c0b7b1f [file] [log] [blame]
// 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.
import 'package:flutter/foundation.dart' show FlutterExceptionHandler;
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
testWidgets('SnackBar control test', (WidgetTester tester) async {
const String helloSnackBar = 'Hello SnackBar';
const Key tapTarget = Key('tap-target');
await tester.pumpWidget(MaterialApp(
home: Scaffold(
body: Builder(
builder: (BuildContext context) {
return GestureDetector(
onTap: () {
Scaffold.of(context)!.showSnackBar(const SnackBar(
content: Text(helloSnackBar),
duration: Duration(seconds: 2),
));
},
behavior: HitTestBehavior.opaque,
child: Container(
height: 100.0,
width: 100.0,
key: tapTarget,
),
);
}
),
),
));
expect(find.text(helloSnackBar), findsNothing);
await tester.tap(find.byKey(tapTarget));
expect(find.text(helloSnackBar), findsNothing);
await tester.pump(); // schedule animation
expect(find.text(helloSnackBar), findsOneWidget);
await tester.pump(); // begin animation
expect(find.text(helloSnackBar), findsOneWidget);
await tester.pump(const Duration(milliseconds: 750)); // 0.75s // animation last frame; two second timer starts here
expect(find.text(helloSnackBar), findsOneWidget);
await tester.pump(const Duration(milliseconds: 750)); // 1.50s
expect(find.text(helloSnackBar), findsOneWidget);
await tester.pump(const Duration(milliseconds: 750)); // 2.25s
expect(find.text(helloSnackBar), findsOneWidget);
await tester.pump(const Duration(milliseconds: 750)); // 3.00s // timer triggers to dismiss snackbar, reverse animation is scheduled
await tester.pump(); // begin animation
expect(find.text(helloSnackBar), findsOneWidget); // frame 0 of dismiss animation
await tester.pump(const Duration(milliseconds: 750)); // 3.75s // last frame of animation, snackbar removed from build
expect(find.text(helloSnackBar), findsNothing);
});
testWidgets('SnackBar control test - ScaffoldMessenger', (WidgetTester tester) async {
const String helloSnackBar = 'Hello SnackBar';
const Key tapTarget = Key('tap-target');
await tester.pumpWidget(MaterialApp(
home: Scaffold(
body: Builder(
builder: (BuildContext context) {
return GestureDetector(
onTap: () {
ScaffoldMessenger.of(context)!.showSnackBar(const SnackBar(
content: Text(helloSnackBar),
duration: Duration(seconds: 2),
));
},
behavior: HitTestBehavior.opaque,
child: Container(
height: 100.0,
width: 100.0,
key: tapTarget,
),
);
}
),
),
));
expect(find.text(helloSnackBar), findsNothing);
await tester.tap(find.byKey(tapTarget));
expect(find.text(helloSnackBar), findsNothing);
await tester.pump(); // schedule animation
expect(find.text(helloSnackBar), findsOneWidget);
await tester.pump(); // begin animation
expect(find.text(helloSnackBar), findsOneWidget);
await tester.pump(const Duration(milliseconds: 750)); // 0.75s // animation last frame; two second timer starts here
expect(find.text(helloSnackBar), findsOneWidget);
await tester.pump(const Duration(milliseconds: 750)); // 1.50s
expect(find.text(helloSnackBar), findsOneWidget);
await tester.pump(const Duration(milliseconds: 750)); // 2.25s
expect(find.text(helloSnackBar), findsOneWidget);
await tester.pump(const Duration(milliseconds: 750)); // 3.00s // timer triggers to dismiss snackbar, reverse animation is scheduled
await tester.pump(); // begin animation
expect(find.text(helloSnackBar), findsOneWidget); // frame 0 of dismiss animation
await tester.pump(const Duration(milliseconds: 750)); // 3.75s // last frame of animation, snackbar removed from build
expect(find.text(helloSnackBar), findsNothing);
});
testWidgets('SnackBar twice test', (WidgetTester tester) async {
int snackBarCount = 0;
const Key tapTarget = Key('tap-target');
await tester.pumpWidget(MaterialApp(
home: Scaffold(
body: Builder(
builder: (BuildContext context) {
return GestureDetector(
onTap: () {
snackBarCount += 1;
Scaffold.of(context)!.showSnackBar(SnackBar(
content: Text('bar$snackBarCount'),
duration: const Duration(seconds: 2),
));
},
behavior: HitTestBehavior.opaque,
child: Container(
height: 100.0,
width: 100.0,
key: tapTarget,
),
);
}
),
),
));
expect(find.text('bar1'), findsNothing);
expect(find.text('bar2'), findsNothing);
await tester.tap(find.byKey(tapTarget)); // queue bar1
await tester.tap(find.byKey(tapTarget)); // queue bar2
expect(find.text('bar1'), findsNothing);
expect(find.text('bar2'), findsNothing);
await tester.pump(); // schedule animation for bar1
expect(find.text('bar1'), findsOneWidget);
expect(find.text('bar2'), findsNothing);
await tester.pump(); // begin animation
expect(find.text('bar1'), findsOneWidget);
expect(find.text('bar2'), findsNothing);
await tester.pump(const Duration(milliseconds: 750)); // 0.75s // animation last frame; two second timer starts here
expect(find.text('bar1'), findsOneWidget);
expect(find.text('bar2'), findsNothing);
await tester.pump(const Duration(milliseconds: 750)); // 1.50s
expect(find.text('bar1'), findsOneWidget);
expect(find.text('bar2'), findsNothing);
await tester.pump(const Duration(milliseconds: 750)); // 2.25s
expect(find.text('bar1'), findsOneWidget);
expect(find.text('bar2'), findsNothing);
await tester.pump(const Duration(milliseconds: 750)); // 3.00s // timer triggers to dismiss snackbar, reverse animation is scheduled
await tester.pump(); // begin animation
expect(find.text('bar1'), findsOneWidget);
expect(find.text('bar2'), findsNothing);
await tester.pump(const Duration(milliseconds: 750)); // 3.75s // last frame of animation, snackbar removed from build, new snack bar put in its place
expect(find.text('bar1'), findsNothing);
expect(find.text('bar2'), findsOneWidget);
await tester.pump(); // begin animation
expect(find.text('bar1'), findsNothing);
expect(find.text('bar2'), findsOneWidget);
await tester.pump(const Duration(milliseconds: 750)); // 4.50s // animation last frame; two second timer starts here
expect(find.text('bar1'), findsNothing);
expect(find.text('bar2'), findsOneWidget);
await tester.pump(const Duration(milliseconds: 750)); // 5.25s
expect(find.text('bar1'), findsNothing);
expect(find.text('bar2'), findsOneWidget);
await tester.pump(const Duration(milliseconds: 750)); // 6.00s
expect(find.text('bar1'), findsNothing);
expect(find.text('bar2'), findsOneWidget);
await tester.pump(const Duration(milliseconds: 750)); // 6.75s // timer triggers to dismiss snackbar, reverse animation is scheduled
await tester.pump(); // begin animation
expect(find.text('bar1'), findsNothing);
expect(find.text('bar2'), findsOneWidget);
await tester.pump(const Duration(milliseconds: 750)); // 7.50s // last frame of animation, snackbar removed from build, new snack bar put in its place
expect(find.text('bar1'), findsNothing);
expect(find.text('bar2'), findsNothing);
});
testWidgets('SnackBar twice test - ScaffoldMessenger', (WidgetTester tester) async {
int snackBarCount = 0;
const Key tapTarget = Key('tap-target');
await tester.pumpWidget(MaterialApp(
home: Scaffold(
body: Builder(
builder: (BuildContext context) {
return GestureDetector(
onTap: () {
snackBarCount += 1;
ScaffoldMessenger.of(context)!.showSnackBar(SnackBar(
content: Text('bar$snackBarCount'),
duration: const Duration(seconds: 2),
));
},
behavior: HitTestBehavior.opaque,
child: Container(
height: 100.0,
width: 100.0,
key: tapTarget,
),
);
}
),
),
));
expect(find.text('bar1'), findsNothing);
expect(find.text('bar2'), findsNothing);
await tester.tap(find.byKey(tapTarget)); // queue bar1
await tester.tap(find.byKey(tapTarget)); // queue bar2
expect(find.text('bar1'), findsNothing);
expect(find.text('bar2'), findsNothing);
await tester.pump(); // schedule animation for bar1
expect(find.text('bar1'), findsOneWidget);
expect(find.text('bar2'), findsNothing);
await tester.pump(); // begin animation
expect(find.text('bar1'), findsOneWidget);
expect(find.text('bar2'), findsNothing);
await tester.pump(const Duration(milliseconds: 750)); // 0.75s // animation last frame; two second timer starts here
expect(find.text('bar1'), findsOneWidget);
expect(find.text('bar2'), findsNothing);
await tester.pump(const Duration(milliseconds: 750)); // 1.50s
expect(find.text('bar1'), findsOneWidget);
expect(find.text('bar2'), findsNothing);
await tester.pump(const Duration(milliseconds: 750)); // 2.25s
expect(find.text('bar1'), findsOneWidget);
expect(find.text('bar2'), findsNothing);
await tester.pump(const Duration(milliseconds: 750)); // 3.00s // timer triggers to dismiss snackbar, reverse animation is scheduled
await tester.pump(); // begin animation
expect(find.text('bar1'), findsOneWidget);
expect(find.text('bar2'), findsNothing);
await tester.pump(const Duration(milliseconds: 750)); // 3.75s // last frame of animation, snackbar removed from build, new snack bar put in its place
expect(find.text('bar1'), findsNothing);
expect(find.text('bar2'), findsOneWidget);
await tester.pump(); // begin animation
expect(find.text('bar1'), findsNothing);
expect(find.text('bar2'), findsOneWidget);
await tester.pump(const Duration(milliseconds: 750)); // 4.50s // animation last frame; two second timer starts here
expect(find.text('bar1'), findsNothing);
expect(find.text('bar2'), findsOneWidget);
await tester.pump(const Duration(milliseconds: 750)); // 5.25s
expect(find.text('bar1'), findsNothing);
expect(find.text('bar2'), findsOneWidget);
await tester.pump(const Duration(milliseconds: 750)); // 6.00s
expect(find.text('bar1'), findsNothing);
expect(find.text('bar2'), findsOneWidget);
await tester.pump(const Duration(milliseconds: 750)); // 6.75s // timer triggers to dismiss snackbar, reverse animation is scheduled
await tester.pump(); // begin animation
expect(find.text('bar1'), findsNothing);
expect(find.text('bar2'), findsOneWidget);
await tester.pump(const Duration(milliseconds: 750)); // 7.50s // last frame of animation, snackbar removed from build, new snack bar put in its place
expect(find.text('bar1'), findsNothing);
expect(find.text('bar2'), findsNothing);
});
testWidgets('SnackBar cancel test', (WidgetTester tester) async {
int snackBarCount = 0;
const Key tapTarget = Key('tap-target');
late int time;
late ScaffoldFeatureController<SnackBar, SnackBarClosedReason> lastController;
await tester.pumpWidget(MaterialApp(
home: Scaffold(
body: Builder(
builder: (BuildContext context) {
return GestureDetector(
onTap: () {
snackBarCount += 1;
lastController = Scaffold.of(context)!.showSnackBar(SnackBar(
content: Text('bar$snackBarCount'),
duration: Duration(seconds: time),
));
},
behavior: HitTestBehavior.opaque,
child: Container(
height: 100.0,
width: 100.0,
key: tapTarget,
),
);
}
),
),
));
expect(find.text('bar1'), findsNothing);
expect(find.text('bar2'), findsNothing);
time = 1000;
await tester.tap(find.byKey(tapTarget)); // queue bar1
final ScaffoldFeatureController<SnackBar, SnackBarClosedReason> firstController = lastController;
time = 2;
await tester.tap(find.byKey(tapTarget)); // queue bar2
expect(find.text('bar1'), findsNothing);
expect(find.text('bar2'), findsNothing);
await tester.pump(); // schedule animation for bar1
expect(find.text('bar1'), findsOneWidget);
expect(find.text('bar2'), findsNothing);
await tester.pump(); // begin animation
expect(find.text('bar1'), findsOneWidget);
expect(find.text('bar2'), findsNothing);
await tester.pump(const Duration(milliseconds: 750)); // 0.75s // animation last frame; two second timer starts here
expect(find.text('bar1'), findsOneWidget);
expect(find.text('bar2'), findsNothing);
await tester.pump(const Duration(milliseconds: 750)); // 1.50s
expect(find.text('bar1'), findsOneWidget);
expect(find.text('bar2'), findsNothing);
await tester.pump(const Duration(milliseconds: 750)); // 2.25s
expect(find.text('bar1'), findsOneWidget);
expect(find.text('bar2'), findsNothing);
await tester.pump(const Duration(milliseconds: 10000)); // 12.25s
expect(find.text('bar1'), findsOneWidget);
expect(find.text('bar2'), findsNothing);
firstController.close(); // snackbar is manually dismissed
await tester.pump(const Duration(milliseconds: 750)); // 13.00s // reverse animation is scheduled
await tester.pump(); // begin animation
expect(find.text('bar1'), findsOneWidget);
expect(find.text('bar2'), findsNothing);
await tester.pump(const Duration(milliseconds: 750)); // 13.75s // last frame of animation, snackbar removed from build, new snack bar put in its place
expect(find.text('bar1'), findsNothing);
expect(find.text('bar2'), findsOneWidget);
await tester.pump(); // begin animation
expect(find.text('bar1'), findsNothing);
expect(find.text('bar2'), findsOneWidget);
await tester.pump(const Duration(milliseconds: 750)); // 14.50s // animation last frame; two second timer starts here
expect(find.text('bar1'), findsNothing);
expect(find.text('bar2'), findsOneWidget);
await tester.pump(const Duration(milliseconds: 750)); // 15.25s
expect(find.text('bar1'), findsNothing);
expect(find.text('bar2'), findsOneWidget);
await tester.pump(const Duration(milliseconds: 750)); // 16.00s
expect(find.text('bar1'), findsNothing);
expect(find.text('bar2'), findsOneWidget);
await tester.pump(const Duration(milliseconds: 750)); // 16.75s // timer triggers to dismiss snackbar, reverse animation is scheduled
await tester.pump(); // begin animation
expect(find.text('bar1'), findsNothing);
expect(find.text('bar2'), findsOneWidget);
await tester.pump(const Duration(milliseconds: 750)); // 17.50s // last frame of animation, snackbar removed from build, new snack bar put in its place
expect(find.text('bar1'), findsNothing);
expect(find.text('bar2'), findsNothing);
});
testWidgets('SnackBar cancel test - ScaffoldMessenger', (WidgetTester tester) async {
int snackBarCount = 0;
const Key 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(
onTap: () {
snackBarCount += 1;
lastController = ScaffoldMessenger.of(context)!.showSnackBar(SnackBar(
content: Text('bar$snackBarCount'),
duration: Duration(seconds: time),
));
},
behavior: HitTestBehavior.opaque,
child: Container(
height: 100.0,
width: 100.0,
key: tapTarget,
),
);
}
),
),
));
expect(find.text('bar1'), findsNothing);
expect(find.text('bar2'), findsNothing);
time = 1000;
await tester.tap(find.byKey(tapTarget)); // queue bar1
final ScaffoldFeatureController<SnackBar, SnackBarClosedReason> firstController = lastController;
time = 2;
await tester.tap(find.byKey(tapTarget)); // queue bar2
expect(find.text('bar1'), findsNothing);
expect(find.text('bar2'), findsNothing);
await tester.pump(); // schedule animation for bar1
expect(find.text('bar1'), findsOneWidget);
expect(find.text('bar2'), findsNothing);
await tester.pump(); // begin animation
expect(find.text('bar1'), findsOneWidget);
expect(find.text('bar2'), findsNothing);
await tester.pump(const Duration(milliseconds: 750)); // 0.75s // animation last frame; two second timer starts here
expect(find.text('bar1'), findsOneWidget);
expect(find.text('bar2'), findsNothing);
await tester.pump(const Duration(milliseconds: 750)); // 1.50s
expect(find.text('bar1'), findsOneWidget);
expect(find.text('bar2'), findsNothing);
await tester.pump(const Duration(milliseconds: 750)); // 2.25s
expect(find.text('bar1'), findsOneWidget);
expect(find.text('bar2'), findsNothing);
await tester.pump(const Duration(milliseconds: 10000)); // 12.25s
expect(find.text('bar1'), findsOneWidget);
expect(find.text('bar2'), findsNothing);
firstController.close(); // snackbar is manually dismissed
await tester.pump(const Duration(milliseconds: 750)); // 13.00s // reverse animation is scheduled
await tester.pump(); // begin animation
expect(find.text('bar1'), findsOneWidget);
expect(find.text('bar2'), findsNothing);
await tester.pump(const Duration(milliseconds: 750)); // 13.75s // last frame of animation, snackbar removed from build, new snack bar put in its place
expect(find.text('bar1'), findsNothing);
expect(find.text('bar2'), findsOneWidget);
await tester.pump(); // begin animation
expect(find.text('bar1'), findsNothing);
expect(find.text('bar2'), findsOneWidget);
await tester.pump(const Duration(milliseconds: 750)); // 14.50s // animation last frame; two second timer starts here
expect(find.text('bar1'), findsNothing);
expect(find.text('bar2'), findsOneWidget);
await tester.pump(const Duration(milliseconds: 750)); // 15.25s
expect(find.text('bar1'), findsNothing);
expect(find.text('bar2'), findsOneWidget);
await tester.pump(const Duration(milliseconds: 750)); // 16.00s
expect(find.text('bar1'), findsNothing);
expect(find.text('bar2'), findsOneWidget);
await tester.pump(const Duration(milliseconds: 750)); // 16.75s // timer triggers to dismiss snackbar, reverse animation is scheduled
await tester.pump(); // begin animation
expect(find.text('bar1'), findsNothing);
expect(find.text('bar2'), findsOneWidget);
await tester.pump(const Duration(milliseconds: 750)); // 17.50s // last frame of animation, snackbar removed from build, new snack bar put in its place
expect(find.text('bar1'), findsNothing);
expect(find.text('bar2'), findsNothing);
});
testWidgets('SnackBar dismiss test', (WidgetTester tester) async {
int snackBarCount = 0;
const Key tapTarget = Key('tap-target');
await tester.pumpWidget(MaterialApp(
home: Scaffold(
body: Builder(
builder: (BuildContext context) {
return GestureDetector(
onTap: () {
snackBarCount += 1;
Scaffold.of(context)!.showSnackBar(SnackBar(
content: Text('bar$snackBarCount'),
duration: const Duration(seconds: 2),
));
},
behavior: HitTestBehavior.opaque,
child: Container(
height: 100.0,
width: 100.0,
key: tapTarget,
),
);
}
),
),
));
expect(find.text('bar1'), findsNothing);
expect(find.text('bar2'), findsNothing);
await tester.tap(find.byKey(tapTarget)); // queue bar1
await tester.tap(find.byKey(tapTarget)); // queue bar2
expect(find.text('bar1'), findsNothing);
expect(find.text('bar2'), findsNothing);
await tester.pump(); // schedule animation for bar1
expect(find.text('bar1'), findsOneWidget);
expect(find.text('bar2'), findsNothing);
await tester.pump(); // begin animation
expect(find.text('bar1'), findsOneWidget);
expect(find.text('bar2'), findsNothing);
await tester.pump(const Duration(milliseconds: 750)); // 0.75s // animation last frame; two second timer starts here
await tester.drag(find.text('bar1'), const Offset(0.0, 50.0));
await tester.pump(); // bar1 dismissed, bar2 begins animating
expect(find.text('bar1'), findsNothing);
expect(find.text('bar2'), findsOneWidget);
});
testWidgets('SnackBar dismiss test - ScaffoldMessenger', (WidgetTester tester) async {
int snackBarCount = 0;
const Key tapTarget = Key('tap-target');
await tester.pumpWidget(MaterialApp(
home: Scaffold(
body: Builder(
builder: (BuildContext context) {
return GestureDetector(
onTap: () {
snackBarCount += 1;
ScaffoldMessenger.of(context)!.showSnackBar(SnackBar(
content: Text('bar$snackBarCount'),
duration: const Duration(seconds: 2),
));
},
behavior: HitTestBehavior.opaque,
child: Container(
height: 100.0,
width: 100.0,
key: tapTarget,
),
);
}
),
),
));
expect(find.text('bar1'), findsNothing);
expect(find.text('bar2'), findsNothing);
await tester.tap(find.byKey(tapTarget)); // queue bar1
await tester.tap(find.byKey(tapTarget)); // queue bar2
expect(find.text('bar1'), findsNothing);
expect(find.text('bar2'), findsNothing);
await tester.pump(); // schedule animation for bar1
expect(find.text('bar1'), findsOneWidget);
expect(find.text('bar2'), findsNothing);
await tester.pump(); // begin animation
expect(find.text('bar1'), findsOneWidget);
expect(find.text('bar2'), findsNothing);
await tester.pump(const Duration(milliseconds: 750)); // 0.75s // animation last frame; two second timer starts here
await tester.drag(find.text('bar1'), const Offset(0.0, 50.0));
await tester.pump(); // bar1 dismissed, bar2 begins animating
expect(find.text('bar1'), findsNothing);
expect(find.text('bar2'), findsOneWidget);
});
testWidgets('SnackBar cannot be tapped twice', (WidgetTester tester) async {
int tapCount = 0;
await tester.pumpWidget(MaterialApp(
home: Scaffold(
body: Builder(
builder: (BuildContext context) {
return GestureDetector(
onTap: () {
Scaffold.of(context)!.showSnackBar(SnackBar(
content: const Text('I am a snack bar.'),
duration: const Duration(seconds: 2),
action: SnackBarAction(
label: 'ACTION',
onPressed: () {
++tapCount;
},
),
));
},
child: const Text('X'),
);
}
),
),
));
await tester.tap(find.text('X'));
await tester.pump(); // start animation
await tester.pump(const Duration(milliseconds: 750));
expect(tapCount, equals(0));
await tester.tap(find.text('ACTION'));
expect(tapCount, equals(1));
await tester.tap(find.text('ACTION'));
expect(tapCount, equals(1));
await tester.pump();
await tester.tap(find.text('ACTION'));
expect(tapCount, equals(1));
});
testWidgets('SnackBar cannot be tapped twice - ScaffoldMessenger', (WidgetTester tester) async {
int 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('Light theme SnackBar has dark background', (WidgetTester tester) async {
final ThemeData lightTheme = ThemeData.light();
await tester.pumpWidget(
MaterialApp(
theme: lightTheme,
home: Scaffold(
body: Builder(
builder: (BuildContext context) {
return GestureDetector(
onTap: () {
Scaffold.of(context)!.showSnackBar(
SnackBar(
content: const Text('I am a snack bar.'),
duration: const Duration(seconds: 2),
action: SnackBarAction(
label: 'ACTION',
onPressed: () { },
),
),
);
},
child: const Text('X'),
);
}
),
),
),
);
await tester.tap(find.text('X'));
await tester.pump(); // start animation
await tester.pump(const Duration(milliseconds: 750));
final RenderPhysicalModel renderModel = tester.renderObject(
find.widgetWithText(Material, 'I am a snack bar.').first
);
// There is a somewhat complicated background color calculation based
// off of the surface color. For the default light theme it
// should be this value.
expect(renderModel.color, equals(const Color(0xFF333333)));
});
testWidgets('Dark theme SnackBar has light background', (WidgetTester tester) async {
final ThemeData darkTheme = ThemeData.dark();
await tester.pumpWidget(
MaterialApp(
theme: darkTheme,
home: Scaffold(
body: Builder(
builder: (BuildContext context) {
return GestureDetector(
onTap: () {
Scaffold.of(context)!.showSnackBar(
SnackBar(
content: const Text('I am a snack bar.'),
duration: const Duration(seconds: 2),
action: SnackBarAction(
label: 'ACTION',
onPressed: () { },
),
),
);
},
child: const Text('X'),
);
}
),
),
),
);
await tester.tap(find.text('X'));
await tester.pump(); // start animation
await tester.pump(const Duration(milliseconds: 750));
final RenderPhysicalModel renderModel = tester.renderObject(
find.widgetWithText(Material, 'I am a snack bar.').first
);
expect(renderModel.color, equals(darkTheme.colorScheme.onSurface));
});
testWidgets('Snackbar margin can be customized', (WidgetTester tester) async {
const double padding = 20.0;
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: Builder(
builder: (BuildContext context) {
return GestureDetector(
onTap: () {
Scaffold.of(context)!.showSnackBar(
SnackBar(
content: const Text('I am a snack bar.'),
margin: const 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 double viewPadding = 50.0;
const double 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: () {
Scaffold.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('SnackbarBehavior.floating is positioned within safe area - ScaffoldMessenger', (WidgetTester tester) async {
const double viewPadding = 50.0;
const double 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 double 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);
// The text is given a vertical padding of 14 already.
expect(snackBarBottomLeft.dy - textBottomLeft.dy, padding + 14);
expect(textTopRight.dy - snackBarTopRight.dy, padding + 14);
});
testWidgets('Snackbar width can be customized', (WidgetTester tester) async {
const double width = 200.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.'),
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 labels can be colored', (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('I am a snack bar.'),
duration: const Duration(seconds: 2),
action: SnackBarAction(
textColor: Colors.lightBlue,
disabledTextColor: Colors.red,
label: 'ACTION',
onPressed: () { },
),
),
);
},
child: const Text('X'),
);
}
),
),
),
);
await tester.tap(find.text('X'));
await tester.pump(); // start animation
await tester.pump(const Duration(milliseconds: 750));
final Element actionTextBox = tester.element(find.text('ACTION'));
final Widget textWidget = actionTextBox.widget;
final DefaultTextStyle defaultTextStyle = DefaultTextStyle.of(actionTextBox);
if (textWidget is Text) {
final TextStyle effectiveStyle = defaultTextStyle.style.merge(textWidget.style);
expect(effectiveStyle.color, Colors.lightBlue);
} else {
expect(false, true);
}
});
testWidgets('SnackBar button text alignment', (WidgetTester tester) async {
await tester.pumpWidget(MaterialApp(
home: MediaQuery(
data: const MediaQueryData(
padding: EdgeInsets.only(
left: 10.0,
top: 20.0,
right: 30.0,
bottom: 40.0,
),
),
child: Scaffold(
body: Builder(
builder: (BuildContext context) {
return GestureDetector(
onTap: () {
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(
'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, 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('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 int defaultFabPadding = 16;
// FAB should be positioned above the SnackBar by the default padding.
expect(fabRect.bottomRight.dy, snackBarTopRight.dy - defaultFabPadding);
});
testWidgets('Floating SnackBar button text alignment', (WidgetTester tester) async {
await tester.pumpWidget(MaterialApp(
theme: ThemeData(
snackBarTheme: const SnackBarThemeData(behavior: SnackBarBehavior.floating,)
),
home: MediaQuery(
data: const MediaQueryData(
padding: EdgeInsets.only(
left: 10.0,
top: 20.0,
right: 30.0,
bottom: 40.0,
),
),
child: Scaffold(
body: Builder(
builder: (BuildContext context) {
return GestureDetector(
onTap: () {
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(
'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, 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('SnackBarClosedReason', (WidgetTester tester) async {
final GlobalKey<ScaffoldMessengerState> scaffoldMessengerKey = GlobalKey<ScaffoldMessengerState>();
bool 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));
// Pop up the snack bar and then let it time out.
await tester.tap(find.text('X'));
await tester.pump(const Duration(milliseconds: 750));
await tester.pump(const Duration(milliseconds: 750));
await tester.pump(const Duration(milliseconds: 1500));
await tester.pump(); // begin animation
await tester.pumpAndSettle(const Duration(seconds: 1));
expect(closedReason, equals(SnackBarClosedReason.timeout));
});
testWidgets('accessible navigation behavior with action', (WidgetTester tester) async {
final GlobalKey<ScaffoldState> scaffoldKey = GlobalKey<ScaffoldState>();
await tester.pumpWidget(MaterialApp(
home: MediaQuery(
data: const MediaQueryData(accessibleNavigation: true),
child: Scaffold(
key: scaffoldKey,
body: Builder(
builder: (BuildContext context) {
return GestureDetector(
onTap: () {
Scaffold.of(context)!.showSnackBar(SnackBar(
content: const Text('snack'),
duration: const Duration(seconds: 1),
action: SnackBarAction(
label: 'ACTION',
onPressed: () { },
),
));
},
child: const Text('X'),
);
},
),
),
),
));
await tester.tap(find.text('X'));
await tester.pump();
// Find action immediately
expect(find.text('ACTION'), findsOneWidget);
// Snackbar doesn't close
await tester.pump(const Duration(seconds: 10));
expect(find.text('ACTION'), findsOneWidget);
await tester.tap(find.text('ACTION'));
await tester.pump();
// Snackbar closes immediately
expect(find.text('ACTION'), findsNothing);
});
testWidgets('accessible navigation behavior with action - ScaffoldMessenger', (WidgetTester tester) async {
final GlobalKey<ScaffoldState> 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 GlobalKey<ScaffoldState> scaffoldKey = GlobalKey<ScaffoldState>();
await tester.pumpWidget(MaterialApp(
home: MediaQuery(
data: const MediaQueryData(accessibleNavigation: true),
child: Scaffold(
key: scaffoldKey,
body: Builder(builder: (BuildContext context) {
return GestureDetector(
onTap: () {
Scaffold.of(context)!.showSnackBar(SnackBar(
content: const Text('snack'),
duration: const Duration(seconds: 1),
action: SnackBarAction(
label: 'ACTION',
onPressed: () { },
),
));
},
child: const Text('X'),
);
}),
),
)
));
await tester.tap(find.text('X'));
await tester.pumpAndSettle();
expect(tester.getSemantics(find.text('snack')), matchesSemantics(
isLiveRegion: true,
hasDismissAction: true,
hasScrollDownAction: true,
hasScrollUpAction: true,
label: 'snack',
textDirection: TextDirection.ltr,
));
handle.dispose();
});
testWidgets('contributes dismiss semantics - ScaffoldMessenger', (WidgetTester tester) async {
final SemanticsHandle handle = tester.ensureSemantics();
final GlobalKey<ScaffoldState> scaffoldKey = GlobalKey<ScaffoldState>();
await tester.pumpWidget(MaterialApp(
home: MediaQuery(
data: const MediaQueryData(accessibleNavigation: true),
child: 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 String helloSnackBar = 'Hello SnackBar';
const Key tapTarget = Key('tap-target');
await tester.pumpWidget(MaterialApp(
home: Scaffold(
body: Builder(
builder: (BuildContext context) {
return GestureDetector(
onTap: () {
ScaffoldMessenger.of(context)!.showSnackBar(const SnackBar(
content: Text(helloSnackBar),
));
},
behavior: HitTestBehavior.opaque,
child: Container(
height: 100.0,
width: 100.0,
key: tapTarget,
),
);
}
),
),
));
expect(find.text(helloSnackBar), findsNothing);
await tester.tap(find.byKey(tapTarget));
expect(find.text(helloSnackBar), findsNothing);
await tester.pump(); // schedule animation
expect(find.text(helloSnackBar), findsOneWidget);
await tester.pump(); // begin animation
expect(find.text(helloSnackBar), findsOneWidget);
await tester.pump(const Duration(milliseconds: 750)); // 0.75s // animation last frame; four second timer starts here
expect(find.text(helloSnackBar), findsOneWidget);
await tester.pump(const Duration(milliseconds: 750)); // 1.50s
expect(find.text(helloSnackBar), findsOneWidget);
await tester.pump(const Duration(milliseconds: 750)); // 2.25s
expect(find.text(helloSnackBar), findsOneWidget);
await tester.pump(const Duration(milliseconds: 750)); // 3.00s
expect(find.text(helloSnackBar), findsOneWidget);
await tester.pump(const Duration(milliseconds: 750)); // 3.75s
expect(find.text(helloSnackBar), findsOneWidget);
await tester.pump(const Duration(milliseconds: 1000)); // 4.75s // timer triggers to dismiss snackbar, reverse animation is scheduled
await tester.pump(); // begin animation
expect(find.text(helloSnackBar), findsOneWidget); // frame 0 of dismiss animation
await tester.pump(const Duration(milliseconds: 750)); // 5.50s // last frame of animation, snackbar removed from build
expect(find.text(helloSnackBar), findsNothing);
});
testWidgets('SnackBar handles updates to accessibleNavigation', (WidgetTester tester) async {
Future<void> boilerplate({ required bool accessibleNavigation }) {
return tester.pumpWidget(MaterialApp(
home: MediaQuery(
data: MediaQueryData(accessibleNavigation: accessibleNavigation),
child: Scaffold(
body: Builder(
builder: (BuildContext context) {
return GestureDetector(
onTap: () {
Scaffold.of(context)!.showSnackBar(SnackBar(
content: const Text('test'),
action: SnackBarAction(label: 'foo', onPressed: () { }),
));
},
behavior: HitTestBehavior.opaque,
child: const Text('X'),
);
}
),
),
),
));
}
await boilerplate(accessibleNavigation: false);
expect(find.text('test'), findsNothing);
await tester.tap(find.text('X'));
await tester.pump(); // schedule animation
expect(find.text('test'), findsOneWidget);
await tester.pump(); // begin animation
await tester.pump(const Duration(milliseconds: 4750)); // 4.75s
expect(find.text('test'), findsOneWidget);
// Enabled accessible navigation
await boilerplate(accessibleNavigation: true);
await tester.pump(const Duration(milliseconds: 4000)); // 8.75s
await tester.pump();
expect(find.text('test'), findsOneWidget);
// disable accessible navigation
await boilerplate(accessibleNavigation: false);
await tester.pumpAndSettle(const Duration(milliseconds: 5750));
expect(find.text('test'), findsNothing);
});
testWidgets('SnackBar handles updates to accessibleNavigation - ScaffoldMessenger', (WidgetTester tester) async {
Future<void> boilerplate({ required bool accessibleNavigation }) {
return tester.pumpWidget(MaterialApp(
home: MediaQuery(
data: MediaQueryData(accessibleNavigation: accessibleNavigation),
child: 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'), findsNothing);
});
testWidgets('Snackbar calls onVisible once', (WidgetTester tester) async {
const Key tapTarget = Key('tap-target');
int called = 0;
await tester.pumpWidget(MaterialApp(
home: Scaffold(
body: Builder(
builder: (BuildContext context) {
return GestureDetector(
onTap: () {
ScaffoldMessenger.of(context)!.showSnackBar(SnackBar(
content: const Text('hello'),
duration: const Duration(seconds: 1),
onVisible: () {
called += 1;
},
));
},
behavior: HitTestBehavior.opaque,
child: Container(
height: 100.0,
width: 100.0,
key: tapTarget,
),
);
},
),
),
));
await tester.tap(find.byKey(tapTarget));
await tester.pump(); // start animation
await tester.pumpAndSettle();
expect(find.text('hello'), findsOneWidget);
expect(called, 1);
});
testWidgets('Snackbar does not call onVisible when it is queued', (WidgetTester tester) async {
const Key tapTarget = Key('tap-target');
int called = 0;
await tester.pumpWidget(MaterialApp(
home: Scaffold(
body: Builder(
builder: (BuildContext context) {
return GestureDetector(
onTap: () {
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: Container(
height: 100.0,
width: 100.0,
key: tapTarget,
),
);
},
),
),
));
await tester.tap(find.byKey(tapTarget));
await tester.pump(); // start animation
await tester.pumpAndSettle();
expect(find.text('hello'), findsOneWidget);
expect(called, 1);
});
group('SnackBar position', () {
for (final SnackBarBehavior behavior in SnackBarBehavior.values) {
final SnackBar 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 UniqueKey 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 $behavior 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: behavior,
),
);
},
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(
padding: EdgeInsets.zero,
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.fixed} should align SnackBar with the top of BottomNavigationBar '
'when Scaffold has a BottomNavigationBar and FloatingActionButton',
(WidgetTester tester) async {
final UniqueKey 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 UniqueKey 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(
'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: () {
Scaffold.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: () {
Scaffold.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('SnackBars hero across transitions when using ScaffoldMessenger', (WidgetTester tester) async {
const String snackBarText = 'hello snackbar';
const String firstHeader = 'home';
const String secondHeader = 'second';
const Key snackTarget = Key('snack-target');
const Key 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: 750));
expect(find.text(snackBarText), findsOneWidget);
expect(find.text(firstHeader), findsNothing);
expect(find.text(secondHeader), findsOneWidget);
});
testWidgets('SnackBars cannot be used by the Scaffold and ScaffoldMessenger at the same time', (WidgetTester tester) async {
await tester.pumpWidget(const MaterialApp(
home: Scaffold(),
));
final ScaffoldMessengerState scaffoldMessengerState = tester.state(find.byType(ScaffoldMessenger));
scaffoldMessengerState.showSnackBar(SnackBar(
content: const Text('ScaffoldMessenger'),
duration: const Duration(seconds: 2),
action: SnackBarAction(label: 'ACTION', onPressed: () {}),
behavior: SnackBarBehavior.floating,
));
final ScaffoldState scaffoldState = tester.state(find.byType(Scaffold));
scaffoldState.showSnackBar(SnackBar(
content: const Text('Scaffold'),
duration: const Duration(seconds: 2),
action: SnackBarAction(label: 'ACTION', onPressed: () {}),
behavior: SnackBarBehavior.floating,
));
final List<dynamic> exceptions = <dynamic>[];
final FlutterExceptionHandler? oldHandler = FlutterError.onError;
FlutterError.onError = (FlutterErrorDetails details) {
exceptions.add(details.exception);
};
await tester.pump();
FlutterError.onError = oldHandler;
expect(exceptions.length, 1);
final AssertionError error = exceptions.first as AssertionError;
expect(error.message, contains('Only one API should be used to manage SnackBars.'));
});
}