Unverified Commit 135bb5d4 authored by Kate Lovett's avatar Kate Lovett Committed by GitHub

Remove deprecated Scaffold SnackBar API (#98549)

parent 1cf5c740
......@@ -2017,195 +2017,8 @@ class ScaffoldState extends State<Scaffold> with TickerProviderStateMixin, Resto
// Used for both the snackbar and material banner APIs
ScaffoldMessengerState? _scaffoldMessenger;
bool? _accessibleNavigation;
// SNACKBAR API
final Queue<ScaffoldFeatureController<SnackBar, SnackBarClosedReason>> _snackBars = Queue<ScaffoldFeatureController<SnackBar, SnackBarClosedReason>>();
AnimationController? _snackBarController;
Timer? _snackBarTimer;
/// [ScaffoldMessengerState.showSnackBar] shows a [SnackBar] at the bottom of
/// the scaffold. This method should not be used, and will be deprecated in
/// the near future..
///
/// A scaffold can show at most one snack bar at a time. If this function is
/// called while another snack bar is already visible, the given snack bar
/// will be added to a queue and displayed after the earlier snack bars have
/// closed.
///
/// To control how long a [SnackBar] remains visible, use [SnackBar.duration].
///
/// To remove the [SnackBar] with an exit animation, use
/// [ScaffoldMessengerState.hideCurrentSnackBar] or call
/// [ScaffoldFeatureController.close] on the returned [ScaffoldFeatureController].
/// To remove a [SnackBar] suddenly (without an animation), use
/// [ScaffoldMessengerState.removeCurrentSnackBar].
///
/// See [ScaffoldMessenger.of] for information about how to obtain the
/// [ScaffoldMessengerState].
///
/// {@tool dartpad}
/// Here is an example of showing a [SnackBar] when the user presses a button.
///
/// ** See code in examples/api/lib/material/scaffold/scaffold_state.show_snack_bar.0.dart **
/// {@end-tool}
///
/// See also:
///
/// * [ScaffoldMessenger], this should be used instead to manage [SnackBar]s.
@Deprecated(
'Use ScaffoldMessenger.showSnackBar. '
'This feature was deprecated after v1.23.0-14.0.pre.',
)
ScaffoldFeatureController<SnackBar, SnackBarClosedReason> showSnackBar(SnackBar snackbar) {
_snackBarController ??= SnackBar.createAnimationController(vsync: this)
..addStatusListener(_handleSnackBarStatusChange);
if (_snackBars.isEmpty) {
assert(_snackBarController!.isDismissed);
_snackBarController!.forward();
}
late ScaffoldFeatureController<SnackBar, SnackBarClosedReason> controller;
controller = ScaffoldFeatureController<SnackBar, SnackBarClosedReason>._(
// We provide a fallback key so that if back-to-back snackbars happen to
// match in structure, material ink splashes and highlights don't survive
// from one to the next.
snackbar.withAnimation(_snackBarController!, fallbackKey: UniqueKey()),
Completer<SnackBarClosedReason>(),
() {
assert(_snackBars.first == controller);
hideCurrentSnackBar();
},
null, // SnackBar doesn't use a builder function so setState() wouldn't rebuild it
);
setState(() {
_snackBars.addLast(controller);
});
return controller;
}
void _handleSnackBarStatusChange(AnimationStatus status) {
switch (status) {
case AnimationStatus.dismissed:
assert(_snackBars.isNotEmpty);
setState(() {
_snackBars.removeFirst();
});
if (_snackBars.isNotEmpty)
_snackBarController!.forward();
break;
case AnimationStatus.completed:
setState(() {
assert(_snackBarTimer == null);
// build will create a new timer if necessary to dismiss the snack bar
});
break;
case AnimationStatus.forward:
case AnimationStatus.reverse:
break;
}
}
/// [ScaffoldMessengerState.removeCurrentSnackBar] removes the current
/// [SnackBar] (if any) immediately. This method should not be used, and will
/// be deprecated in the near future.
///
/// The removed snack bar does not run its normal exit animation. If there are
/// any queued snack bars, they begin their entrance animation immediately.
///
/// See also:
///
/// * [ScaffoldMessenger], this should be used instead to manage [SnackBar]s.
@Deprecated(
'Use ScaffoldMessenger.removeCurrentSnackBar. '
'This feature was deprecated after v1.23.0-14.0.pre.',
)
void removeCurrentSnackBar({ SnackBarClosedReason reason = SnackBarClosedReason.remove }) {
assert(reason != null);
// SnackBars and SnackBarActions can call to hide and remove themselves, but
// they are not aware of who presented them, the Scaffold or the
// ScaffoldMessenger. As such, when the SnackBar classes call upon Scaffold
// to remove (the current default), we should re-direct to the
// ScaffoldMessenger here if that is where the SnackBar originated from.
if (_messengerSnackBar != null) {
// ScaffoldMessenger is presenting SnackBars.
assert(debugCheckHasScaffoldMessenger(context));
assert(
_scaffoldMessenger != null,
'A SnackBar was shown by the ScaffoldMessenger, but has been called upon '
'to be removed from a Scaffold that is not registered with a '
'ScaffoldMessenger, this can happen if a Scaffold has been rebuilt '
'without an ancestor ScaffoldMessenger.',
);
_scaffoldMessenger!.removeCurrentSnackBar(reason: reason);
return;
}
if (_snackBars.isEmpty)
return;
final Completer<SnackBarClosedReason> completer = _snackBars.first._completer;
if (!completer.isCompleted)
completer.complete(reason);
_snackBarTimer?.cancel();
_snackBarTimer = null;
_snackBarController!.value = 0.0;
}
/// [ScaffoldMessengerState.hideCurrentSnackBar] removes the current
/// [SnackBar] by running its normal exit animation. This method should not be
/// used, and will be deprecated in the near future.
///
/// The closed completer is called after the animation is complete.
///
/// See also:
///
/// * [ScaffoldMessenger], this should be used instead to manage [SnackBar]s.
@Deprecated(
'Use ScaffoldMessenger.hideCurrentSnackBar. '
'This feature was deprecated after v1.23.0-14.0.pre.',
)
void hideCurrentSnackBar({ SnackBarClosedReason reason = SnackBarClosedReason.hide }) {
assert(reason != null);
// SnackBars and SnackBarActions can call to hide and remove themselves, but
// they are not aware of who presented them, the Scaffold or the
// ScaffoldMessenger. As such, when the SnackBar classes call upon Scaffold
// to remove (the current default), we should re-direct to the
// ScaffoldMessenger here if that is where the SnackBar originated from.
if (_messengerSnackBar != null) {
// ScaffoldMessenger is presenting SnackBars.
assert(debugCheckHasScaffoldMessenger(context));
assert(
_scaffoldMessenger != null,
'A SnackBar was shown by the ScaffoldMessenger, but has been called upon '
'to be removed from a Scaffold that is not registered with a '
'ScaffoldMessenger, this can happen if a Scaffold has been rebuilt '
'without an ancestor ScaffoldMessenger.',
);
_scaffoldMessenger!.hideCurrentSnackBar(reason: reason);
return;
}
if (_snackBars.isEmpty || _snackBarController!.status == AnimationStatus.dismissed)
return;
final MediaQueryData mediaQuery = MediaQuery.of(context);
final Completer<SnackBarClosedReason> completer = _snackBars.first._completer;
if (mediaQuery.accessibleNavigation) {
_snackBarController!.value = 0.0;
completer.complete(reason);
} else {
_snackBarController!.reverse().then<void>((void value) {
assert(mounted);
if (!completer.isCompleted)
completer.complete(reason);
});
}
_snackBarTimer?.cancel();
_snackBarTimer = null;
}
// The _messengerSnackBar represents the current SnackBar being managed by
// the ScaffoldMessenger, instead of the Scaffold.
ScaffoldFeatureController<SnackBar, SnackBarClosedReason>? _messengerSnackBar;
// This is used to update the _messengerSnackBar by the ScaffoldMessenger.
......@@ -2672,31 +2485,12 @@ class ScaffoldState extends State<Scaffold> with TickerProviderStateMixin, Resto
_scaffoldMessenger = currentScaffoldMessenger;
_scaffoldMessenger?._register(this);
// TODO(Piinks): Remove old SnackBar API after migrating ScaffoldMessenger
final MediaQueryData mediaQuery = MediaQuery.of(context);
// If we transition from accessible navigation to non-accessible navigation
// and there is a SnackBar that would have timed out that has already
// completed its timer, dismiss that SnackBar. If the timer hasn't finished
// yet, let it timeout as normal.
if ((_accessibleNavigation ?? false)
&& !mediaQuery.accessibleNavigation
&& _snackBarTimer != null
&& !_snackBarTimer!.isActive) {
hideCurrentSnackBar(reason: SnackBarClosedReason.timeout);
}
_accessibleNavigation = mediaQuery.accessibleNavigation;
_maybeBuildPersistentBottomSheet();
super.didChangeDependencies();
}
@override
void dispose() {
// TODO(Piinks): Remove old SnackBar API after migrating ScaffoldMessenger
_snackBarController?.dispose();
_snackBarTimer?.cancel();
_snackBarTimer = null;
_geometryNotifier.dispose();
_floatingActionButtonMoveController.dispose();
_floatingActionButtonVisibilityController.dispose();
......@@ -2817,31 +2611,6 @@ class ScaffoldState extends State<Scaffold> with TickerProviderStateMixin, Resto
final ThemeData themeData = Theme.of(context);
final TextDirection textDirection = Directionality.of(context);
// TODO(Piinks): Remove old SnackBar API after migrating ScaffoldMessenger
_accessibleNavigation = mediaQuery.accessibleNavigation;
if (_snackBars.isNotEmpty) {
final ModalRoute<dynamic>? route = ModalRoute.of(context);
if (route == null || route.isCurrent) {
if (_snackBarController!.isCompleted && _snackBarTimer == null) {
final SnackBar snackBar = _snackBars.first._widget;
_snackBarTimer = Timer(snackBar.duration, () {
assert(
_snackBarController!.status == AnimationStatus.forward ||
_snackBarController!.status == AnimationStatus.completed,
);
// Look up MediaQuery again in case the setting changed.
final MediaQueryData mediaQuery = MediaQuery.of(context);
if (mediaQuery.accessibleNavigation && snackBar.action != null)
return;
hideCurrentSnackBar(reason: SnackBarClosedReason.timeout);
});
}
} else {
_snackBarTimer?.cancel();
_snackBarTimer = null;
}
}
final List<LayoutId> children = <LayoutId>[];
_addIfNonNull(
children,
......@@ -2895,15 +2664,6 @@ class ScaffoldState extends State<Scaffold> with TickerProviderStateMixin, Resto
bool isSnackBarFloating = false;
double? snackBarWidth;
// We should only be using one API for SnackBars. Currently, we can use the
// Scaffold, which creates a SnackBar queue (_snackBars), or the
// ScaffoldMessenger, which sends a SnackBar to descendant Scaffolds.
// (_messengerSnackBar).
assert(
_snackBars.isEmpty || _messengerSnackBar == null,
'Only one API should be used to manage SnackBars. The ScaffoldMessenger is '
'the preferred API instead of the Scaffold methods.',
);
if (_currentBottomSheet != null || _dismissedBottomSheets.isNotEmpty) {
final Widget stack = Stack(
......@@ -2944,27 +2704,6 @@ class ScaffoldState extends State<Scaffold> with TickerProviderStateMixin, Resto
);
}
// SnackBar set by Scaffold
// TODO(Piinks): Remove old SnackBar API after migrating ScaffoldMessenger
if (_snackBars.isNotEmpty) {
final SnackBarBehavior snackBarBehavior = _snackBars.first._widget.behavior
?? themeData.snackBarTheme.behavior
?? SnackBarBehavior.fixed;
isSnackBarFloating = snackBarBehavior == SnackBarBehavior.floating;
snackBarWidth = _snackBars.first._widget.width;
_addIfNonNull(
children,
_snackBars.first._widget,
_ScaffoldSlot.snackBar,
removeLeftPadding: false,
removeTopPadding: true,
removeRightPadding: false,
removeBottomPadding: widget.bottomNavigationBar != null || widget.persistentFooterButtons != null,
maintainBottomViewPadding: !_resizeToAvoidBottomInset,
);
}
bool extendBodyBehindMaterialBanner = false;
// MaterialBanner set by ScaffoldMessenger
if (_messengerMaterialBanner != null) {
......
......@@ -122,7 +122,7 @@ class _SnackBarActionState extends State<SnackBarAction> {
_haveTriggeredAction = true;
});
widget.onPressed();
Scaffold.of(context).hideCurrentSnackBar(reason: SnackBarClosedReason.action);
ScaffoldMessenger.of(context).hideCurrentSnackBar(reason: SnackBarClosedReason.action);
}
@override
......@@ -583,14 +583,14 @@ class _SnackBarState extends State<SnackBar> {
container: true,
liveRegion: true,
onDismiss: () {
Scaffold.of(context).removeCurrentSnackBar(reason: SnackBarClosedReason.dismiss);
ScaffoldMessenger.of(context).removeCurrentSnackBar(reason: SnackBarClosedReason.dismiss);
},
child: Dismissible(
key: const Key('dismissible'),
direction: widget.dismissDirection,
resizeDuration: null,
onDismissed: (DismissDirection direction) {
Scaffold.of(context).removeCurrentSnackBar(reason: SnackBarClosedReason.swipe);
ScaffoldMessenger.of(context).removeCurrentSnackBar(reason: SnackBarClosedReason.swipe);
},
child: snackBar,
),
......
......@@ -263,8 +263,10 @@ void main() {
),
),
));
// The Scaffold should assert we still have an ancestor ScaffoldMessenger in
// order to dismiss the SnackBar from the ScaffoldMessenger.
// Tap SnackBarAction to dismiss.
// The SnackBarAction should assert we still have an ancestor
// ScaffoldMessenger in order to dismiss the SnackBar from the
// Scaffold.
await tester.tap(find.text('Test'));
FlutterError.onError = oldHandler;
......@@ -286,11 +288,67 @@ void main() {
expect(error.toStringDeep(), equalsIgnoringHashCodes(
'FlutterError\n'
' No ScaffoldMessenger widget found.\n'
' Scaffold widgets require a ScaffoldMessenger widget ancestor.\n'
' SnackBarAction widgets require a ScaffoldMessenger widget\n'
' ancestor.\n'
' The specific widget that could not find a ScaffoldMessenger\n'
' ancestor was:\n'
' Scaffold-[LabeledGlobalKey<ScaffoldState>#00829]\n'
' SnackBarAction\n'
' The ancestors of this widget were:\n'
' TextButtonTheme\n'
' Padding\n'
' Row\n'
' Padding\n'
' MediaQuery\n'
' Padding\n'
' SafeArea\n'
' FadeTransition\n'
' IconTheme\n'
' IconTheme\n'
' _InheritedCupertinoTheme\n'
' CupertinoTheme\n'
' _InheritedTheme\n'
' Theme\n'
' DefaultTextStyle\n'
' AnimatedDefaultTextStyle\n'
' _InkFeatures-[GlobalKey#00000 ink renderer]\n'
' NotificationListener<LayoutChangedNotification>\n'
' PhysicalModel\n'
' AnimatedPhysicalModel\n'
' Material\n'
' FractionalTranslation\n'
' SlideTransition\n'
' Listener\n'
' _GestureSemantics\n'
' RawGestureDetector\n'
' GestureDetector\n'
" Dismissible-[<'dismissible'>]\n"
' Semantics\n'
' Align\n'
' AnimatedBuilder\n'
' ClipRect\n'
' KeyedSubtree-[GlobalKey#00000]\n'
' _EffectiveTickerMode\n'
' TickerMode\n'
' Offstage\n'
' SizedBox\n'
' Hero\n'
' SnackBar-[#00000]\n'
' MediaQuery\n'
' LayoutId-[<_ScaffoldSlot.snackBar>]\n'
' CustomMultiChildLayout\n'
' AnimatedBuilder\n'
' DefaultTextStyle\n'
' AnimatedDefaultTextStyle\n'
' _InkFeatures-[GlobalKey#00000 ink renderer]\n'
' NotificationListener<LayoutChangedNotification>\n'
' PhysicalModel\n'
' AnimatedPhysicalModel\n'
' Material\n'
' _ScrollMetricsNotificationObserverScope\n'
' NotificationListener<ScrollMetricsNotification>\n'
' ScrollMetricsNotificationObserver\n'
' _ScaffoldScope\n'
' Scaffold-[LabeledGlobalKey<ScaffoldState>#00000]\n'
' MediaQuery\n'
' Directionality\n'
' [root]\n'
......
......@@ -8,58 +8,12 @@
import 'dart:ui';
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(
key: tapTarget,
onTap: () {
Scaffold.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 control test - ScaffoldMessenger', (WidgetTester tester) async {
const String helloSnackBar = 'Hello SnackBar';
const Key tapTarget = Key('tap-target');
await tester.pumpWidget(MaterialApp(
......@@ -105,81 +59,6 @@ void main() {
});
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(
key: tapTarget,
onTap: () {
snackBarCount += 1;
Scaffold.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 twice test - ScaffoldMessenger', (WidgetTester tester) async {
int snackBarCount = 0;
const Key tapTarget = Key('tap-target');
await tester.pumpWidget(MaterialApp(
......@@ -255,92 +134,6 @@ void main() {
});
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(
key: tapTarget,
onTap: () {
snackBarCount += 1;
lastController = Scaffold.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 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;
......@@ -432,48 +225,6 @@ void main() {
late double width;
int snackBarCount = 0;
await tester.pumpWidget(MaterialApp(
home: Scaffold(
body: Builder(
builder: (BuildContext context) {
width = MediaQuery.of(context).size.width;
return GestureDetector(
key: tapTarget,
onTap: () {
snackBarCount += 1;
Scaffold.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 dismiss test - ScaffoldMessenger', (WidgetTester tester) async {
const Key tapTarget = Key('tap-target');
late DismissDirection dismissDirection;
late double width;
int snackBarCount = 0;
await tester.pumpWidget(MaterialApp(
home: Scaffold(
body: Builder(
......@@ -511,45 +262,6 @@ void main() {
});
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(
......@@ -598,7 +310,7 @@ void main() {
builder: (BuildContext context) {
return GestureDetector(
onTap: () {
Scaffold.of(context).showSnackBar(
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: const Text('I am a snack bar.'),
duration: const Duration(seconds: 2),
......@@ -640,7 +352,7 @@ void main() {
builder: (BuildContext context) {
return GestureDetector(
onTap: () {
Scaffold.of(context).showSnackBar(
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: const Text('I am a snack bar.'),
duration: const Duration(seconds: 2),
......@@ -679,7 +391,7 @@ void main() {
builder: (BuildContext context) {
return GestureDetector(
onTap: () {
Scaffold.of(context).showSnackBar(
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: const Text('I am a snack bar.'),
duration: const Duration(seconds: 2),
......@@ -809,7 +521,7 @@ void main() {
themeBeforeSnackBar = Theme.of(context);
return GestureDetector(
onTap: () {
Scaffold.of(context).showSnackBar(
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Builder(
builder: (BuildContext context) {
......@@ -853,7 +565,7 @@ void main() {
builder: (BuildContext context) {
return GestureDetector(
onTap: () {
Scaffold.of(context).showSnackBar(
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('I am a snack bar.'),
margin: EdgeInsets.all(padding),
......@@ -885,53 +597,6 @@ void main() {
});
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(
......@@ -1452,47 +1117,6 @@ void main() {
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),
......@@ -1537,47 +1161,6 @@ void main() {
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),
......@@ -1666,54 +1249,6 @@ void main() {
});
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(
......@@ -2127,7 +1662,7 @@ void main() {
builder: (BuildContext context) {
return GestureDetector(
onTap: () {
Scaffold.of(context).showSnackBar(SnackBar(
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: const Text('Some content'),
behavior: SnackBarBehavior.fixed,
action: SnackBarAction(
......@@ -2164,7 +1699,7 @@ void main() {
builder: (BuildContext context) {
return GestureDetector(
onTap: () {
Scaffold.of(context).showSnackBar(SnackBar(
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: const Text('Some content'),
behavior: SnackBarBehavior.floating,
action: SnackBarAction(
......@@ -2260,39 +1795,6 @@ void main() {
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.'));
});
testWidgets('SnackBars should be shown above the bottomSheet', (WidgetTester tester) async {
await tester.pumpWidget(const MaterialApp(
home: Scaffold(
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment