From e29f2b98af5d0e2aedc4b60afcbf85ba52997d6f Mon Sep 17 00:00:00 2001 From: Bruno Leroux Date: Wed, 19 Jul 2023 00:10:17 +0200 Subject: [PATCH] Update SnackBar tests for M2/M3 (#130717) This PR updates unit tests for `SnackBar` to have M2 and M3 versions. More info in https://github.com/flutter/flutter/issues/127064 --- .../flutter/test/material/snack_bar_test.dart | 671 ++++++++++++++++-- 1 file changed, 622 insertions(+), 49 deletions(-) diff --git a/packages/flutter/test/material/snack_bar_test.dart b/packages/flutter/test/material/snack_bar_test.dart index 0cd54790713..9436f743b4e 100644 --- a/packages/flutter/test/material/snack_bar_test.dart +++ b/packages/flutter/test/material/snack_bar_test.dart @@ -10,6 +10,7 @@ library; import 'dart:async'; import 'dart:ui'; +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -302,7 +303,7 @@ void main() { expect(tapCount, equals(1)); }); - testWidgets('Light theme SnackBar has dark background', (WidgetTester tester) async { + testWidgets('Material2 - Light theme SnackBar has dark background', (WidgetTester tester) async { final ThemeData lightTheme = ThemeData.light(useMaterial3: false); await tester.pumpWidget( MaterialApp( @@ -344,6 +345,45 @@ void main() { expect(renderModel.color, equals(const Color(0xFF333333))); }); + testWidgets('Material3 - Light theme SnackBar has dark background', (WidgetTester tester) async { + final ThemeData lightTheme = ThemeData.light(useMaterial3: true); + await tester.pumpWidget( + MaterialApp( + theme: lightTheme, + home: Scaffold( + body: Builder( + builder: (BuildContext context) { + return GestureDetector( + onTap: () { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: const Text('I am a snack bar.'), + duration: const Duration(seconds: 2), + action: SnackBarAction( + label: 'ACTION', + onPressed: () { }, + ), + ), + ); + }, + child: const Text('X'), + ); + }, + ), + ), + ), + ); + + await tester.tap(find.text('X')); + await tester.pump(); // start animation + await tester.pump(const Duration(milliseconds: 750)); + + final Finder material = find.widgetWithText(Material, 'I am a snack bar.').first; + final RenderPhysicalModel renderModel = tester.renderObject(material); + + expect(renderModel.color, equals(lightTheme.colorScheme.inverseSurface)); + }); + testWidgets('Dark theme SnackBar has light background', (WidgetTester tester) async { final ThemeData darkTheme = ThemeData.dark(); await tester.pumpWidget( @@ -383,7 +423,7 @@ void main() { expect(renderModel.color, equals(darkTheme.colorScheme.onSurface)); }); - testWidgets('Dark theme SnackBar has primary text buttons', (WidgetTester tester) async { + testWidgets('Material2 - Dark theme SnackBar has primary text buttons', (WidgetTester tester) async { final ThemeData darkTheme = ThemeData.dark(useMaterial3: false); await tester.pumpWidget( MaterialApp( @@ -422,6 +462,45 @@ void main() { expect(buttonTextStyle.color, equals(darkTheme.colorScheme.primary)); }); + testWidgets('Material3 - Dark theme SnackBar has primary text buttons', (WidgetTester tester) async { + final ThemeData darkTheme = ThemeData.dark(useMaterial3: true); + await tester.pumpWidget( + MaterialApp( + theme: darkTheme, + home: Scaffold( + body: Builder( + builder: (BuildContext context) { + return GestureDetector( + onTap: () { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: const Text('I am a snack bar.'), + duration: const Duration(seconds: 2), + action: SnackBarAction( + label: 'ACTION', + onPressed: () { }, + ), + ), + ); + }, + child: const Text('X'), + ); + }, + ), + ), + ), + ); + + await tester.tap(find.text('X')); + await tester.pump(); // start animation + await tester.pump(const Duration(milliseconds: 750)); + + final TextStyle buttonTextStyle = tester.widget( + find.descendant(of: find.text('ACTION'), matching: find.byType(RichText)) + ).text.style!; + expect(buttonTextStyle.color, equals(darkTheme.colorScheme.inversePrimary)); + }); + testWidgets('SnackBar should inherit theme data from its ancestor.', (WidgetTester tester) async { final SliderThemeData sliderTheme = SliderThemeData.fromPrimaryColors( primaryColor: Colors.black, @@ -767,9 +846,7 @@ void main() { expect(snackBarBottomRight.dx, (800 + width) / 2); // Device width is 800. }); - testWidgets( - 'Snackbar width customization takes preference of widget over theme', - (WidgetTester tester) async { + testWidgets('Snackbar width customization takes preference of widget over theme', (WidgetTester tester) async { const double themeWidth = 200.0; const double widgetWidth = 400.0; await tester.pumpWidget( @@ -812,9 +889,10 @@ void main() { expect(snackBarBottomRight.dx, (800 + widgetWidth) / 2); // Device width is 800. }); - testWidgets('Snackbar labels can be colored as MaterialColor (Material 2)', (WidgetTester tester) async { + testWidgets('Material2 - Snackbar labels can be colored as MaterialColor', (WidgetTester tester) async { await tester.pumpWidget( MaterialApp( + theme: ThemeData(useMaterial3: false), home: Scaffold( body: Builder( builder: (BuildContext context) { @@ -856,8 +934,7 @@ void main() { } }); - testWidgets('Snackbar labels can be colored as MaterialColor (Material 3)', - (WidgetTester tester) async { + testWidgets('Material3 - Snackbar labels can be colored as MaterialColor', (WidgetTester tester) async { const MaterialColor usedColor = Colors.teal; await tester.pumpWidget( @@ -960,7 +1037,7 @@ void main() { } }); - testWidgets('SnackBar button text alignment', (WidgetTester tester) async { + testWidgets('Material2 - SnackBar button text alignment', (WidgetTester tester) async { await tester.pumpWidget(MaterialApp( theme: ThemeData(useMaterial3: false), home: MediaQuery( @@ -1008,8 +1085,56 @@ void main() { expect(snackBarBottomRight.dy - actionTextBottomRight.dy, 17.0 + 40.0); // margin + bottom padding }); + testWidgets('Material3 - SnackBar button text alignment', (WidgetTester tester) async { + await tester.pumpWidget(MaterialApp( + theme: ThemeData(useMaterial3: true), + home: MediaQuery( + data: const MediaQueryData( + padding: EdgeInsets.only( + left: 10.0, + top: 20.0, + right: 30.0, + bottom: 40.0, + ), + ), + child: Scaffold( + body: Builder( + builder: (BuildContext context) { + return GestureDetector( + onTap: () { + ScaffoldMessenger.of(context).showSnackBar(SnackBar( + content: const Text('I am a snack bar.'), + duration: const Duration(seconds: 2), + action: SnackBarAction(label: 'ACTION', onPressed: () { }), + )); + }, + child: const Text('X'), + ); + }, + ), + ), + ), + )); + await tester.tap(find.text('X')); + await tester.pump(); // start animation + await tester.pump(const Duration(milliseconds: 750)); // Animation last frame. + + final Offset textBottomLeft = tester.getBottomLeft(find.text('I am a snack bar.')); + final Offset textBottomRight = tester.getBottomRight(find.text('I am a snack bar.')); + final Offset actionTextBottomLeft = tester.getBottomLeft(find.text('ACTION')); + final Offset actionTextBottomRight = tester.getBottomRight(find.text('ACTION')); + final Offset snackBarBottomLeft = tester.getBottomLeft(find.byType(SnackBar)); + final Offset snackBarBottomRight = tester.getBottomRight(find.byType(SnackBar)); + + expect(textBottomLeft.dx - snackBarBottomLeft.dx, 24.0 + 10.0); // margin + left padding + expect(snackBarBottomLeft.dy - textBottomLeft.dy, 14.0 + 40.0); // margin + bottom padding + expect(actionTextBottomLeft.dx - textBottomRight.dx, 24.0 + 12.0); // action padding + margin + expect(snackBarBottomRight.dx - actionTextBottomRight.dx, 24.0 + 12.0 + 30.0); // action (padding + margin) + right padding + expect(snackBarBottomRight.dy - actionTextBottomRight.dy, 14.0 + 40.0); // margin + bottom padding + }); + testWidgets( - 'Custom padding between SnackBar and its contents when set to SnackBarBehavior.fixed', + 'Material2 - Custom padding between SnackBar and its contents when set to SnackBarBehavior.fixed', (WidgetTester tester) async { await tester.pumpWidget(MaterialApp( theme: ThemeData(useMaterial3: false), @@ -1065,6 +1190,63 @@ void main() { }, ); + testWidgets( + 'Material3 - Custom padding between SnackBar and its contents when set to SnackBarBehavior.fixed', + (WidgetTester tester) async { + await tester.pumpWidget(MaterialApp( + theme: ThemeData(useMaterial3: true), + 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(icon: Icon(Icons.favorite), label: 'Animutation'), + BottomNavigationBarItem(icon: Icon(Icons.block), label: 'Zombo.com'), + ], + ), + body: Builder( + builder: (BuildContext context) { + return GestureDetector( + onTap: () { + ScaffoldMessenger.of(context).showSnackBar(SnackBar( + content: const Text('I am a snack bar.'), + duration: const Duration(seconds: 2), + action: SnackBarAction(label: 'ACTION', onPressed: () {}), + )); + }, + child: const Text('X'), + ); + }, + ), + ), + ), + )); + await tester.tap(find.text('X')); + await tester.pump(); // start animation + await tester.pump(const Duration(milliseconds: 750)); // Animation last frame. + + final Offset textBottomLeft = tester.getBottomLeft(find.text('I am a snack bar.')); + final Offset textBottomRight = tester.getBottomRight(find.text('I am a snack bar.')); + final Offset actionTextBottomLeft = tester.getBottomLeft(find.text('ACTION')); + final Offset actionTextBottomRight = tester.getBottomRight(find.text('ACTION')); + final Offset snackBarBottomLeft = tester.getBottomLeft(find.byType(SnackBar)); + final Offset snackBarBottomRight = tester.getBottomRight(find.byType(SnackBar)); + + expect(textBottomLeft.dx - snackBarBottomLeft.dx, 24.0 + 10.0); // margin + left padding + expect(snackBarBottomLeft.dy - textBottomLeft.dy, 14.0); // margin (with no bottom padding) + expect(actionTextBottomLeft.dx - textBottomRight.dx, 24.0 + 12.0); // action padding + margin + expect(snackBarBottomRight.dx - actionTextBottomRight.dx, 24.0 + 12.0 + 30.0); // action (padding + margin) + right padding + expect(snackBarBottomRight.dy - actionTextBottomRight.dy, 14.0); // margin (with no bottom padding) + }, + ); + testWidgets('SnackBar should push FloatingActionButton above', (WidgetTester tester) async { await tester.pumpWidget(MaterialApp( home: MediaQuery( @@ -1120,7 +1302,7 @@ void main() { expect(fabRect.bottomRight.dy, snackBarTopRight.dy - defaultFabPadding); }); - testWidgets('Floating SnackBar button text alignment', (WidgetTester tester) async { + testWidgets('Material2 - Floating SnackBar button text alignment', (WidgetTester tester) async { await tester.pumpWidget(MaterialApp( theme: ThemeData( useMaterial3: false, @@ -1171,8 +1353,59 @@ void main() { expect(snackBarBottomRight.dy - actionTextBottomRight.dy, 27.0); // margin (with no bottom padding) }); + testWidgets('Material3 - Floating SnackBar button text alignment', (WidgetTester tester) async { + await tester.pumpWidget(MaterialApp( + theme: ThemeData( + useMaterial3: true, + snackBarTheme: const SnackBarThemeData(behavior: SnackBarBehavior.floating), + ), + home: MediaQuery( + data: const MediaQueryData( + padding: EdgeInsets.only( + left: 10.0, + top: 20.0, + right: 30.0, + bottom: 40.0, + ), + ), + child: Scaffold( + body: Builder( + builder: (BuildContext context) { + return GestureDetector( + onTap: () { + ScaffoldMessenger.of(context).showSnackBar(SnackBar( + content: const Text('I am a snack bar.'), + duration: const Duration(seconds: 2), + action: SnackBarAction(label: 'ACTION', onPressed: () {}), + )); + }, + child: const Text('X'), + ); + }, + ), + ), + ), + )); + await tester.tap(find.text('X')); + await tester.pump(); // start animation + await tester.pump(const Duration(milliseconds: 750)); // Animation last frame. + + final Offset textBottomLeft = tester.getBottomLeft(find.text('I am a snack bar.')); + final Offset textBottomRight = tester.getBottomRight(find.text('I am a snack bar.')); + final Offset actionTextBottomLeft = tester.getBottomLeft(find.text('ACTION')); + final Offset actionTextBottomRight = tester.getBottomRight(find.text('ACTION')); + final Offset snackBarBottomLeft = tester.getBottomLeft(find.byType(SnackBar)); + final Offset snackBarBottomRight = tester.getBottomRight(find.byType(SnackBar)); + + expect(textBottomLeft.dx - snackBarBottomLeft.dx, 31.0 + 10.0); // margin + left padding + expect(snackBarBottomLeft.dy - textBottomLeft.dy, 24.0); // margin (with no bottom padding) + expect(actionTextBottomLeft.dx - textBottomRight.dx, 16.0 + 8.0); // action padding + margin + expect(snackBarBottomRight.dx - actionTextBottomRight.dx, 31.0 + 30.0 + 8.0); // margin + right (padding + margin) + expect(snackBarBottomRight.dy - actionTextBottomRight.dy, 24.0); // margin (with no bottom padding) + }); + testWidgets( - 'Custom padding between SnackBar and its contents when set to SnackBarBehavior.floating', + 'Material2 - Custom padding between SnackBar and its contents when set to SnackBarBehavior.floating', (WidgetTester tester) async { await tester.pumpWidget(MaterialApp( theme: ThemeData( @@ -1231,6 +1464,66 @@ void main() { }, ); + testWidgets( + 'Material3 - Custom padding between SnackBar and its contents when set to SnackBarBehavior.floating', + (WidgetTester tester) async { + await tester.pumpWidget(MaterialApp( + theme: ThemeData( + useMaterial3: true, + 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(icon: Icon(Icons.favorite), label: 'Animutation'), + BottomNavigationBarItem(icon: Icon(Icons.block), label: 'Zombo.com'), + ], + ), + body: Builder( + builder: (BuildContext context) { + return GestureDetector( + onTap: () { + ScaffoldMessenger.of(context).showSnackBar(SnackBar( + content: const Text('I am a snack bar.'), + duration: const Duration(seconds: 2), + action: SnackBarAction(label: 'ACTION', onPressed: () {}), + )); + }, + child: const Text('X'), + ); + }, + ), + ), + ), + )); + await tester.tap(find.text('X')); + await tester.pump(); // start animation + await tester.pump(const Duration(milliseconds: 750)); // Animation last frame. + + final Offset textBottomLeft = tester.getBottomLeft(find.text('I am a snack bar.')); + final Offset textBottomRight = tester.getBottomRight(find.text('I am a snack bar.')); + final Offset actionTextBottomLeft = tester.getBottomLeft(find.text('ACTION')); + final Offset actionTextBottomRight = tester.getBottomRight(find.text('ACTION')); + final Offset snackBarBottomLeft = tester.getBottomLeft(find.byType(SnackBar)); + final Offset snackBarBottomRight = tester.getBottomRight(find.byType(SnackBar)); + + expect(textBottomLeft.dx - snackBarBottomLeft.dx, 31.0 + 10.0); // margin + left padding + expect(snackBarBottomLeft.dy - textBottomLeft.dy, 24.0); // margin (with no bottom padding) + expect(actionTextBottomLeft.dx - textBottomRight.dx, 16.0 + 8.0); // action (margin + padding) + expect(snackBarBottomRight.dx - actionTextBottomRight.dx, 31.0 + 30.0 + 8.0); // margin + right (padding + margin) + expect(snackBarBottomRight.dy - actionTextBottomRight.dy, 24.0); // margin (with no bottom padding) + }, + ); + testWidgets('SnackBarClosedReason', (WidgetTester tester) async { final GlobalKey scaffoldMessengerKey = GlobalKey(); bool actionPressed = false; @@ -1858,16 +2151,12 @@ void main() { await tester.pumpAndSettle(); // Have the SnackBar fully animate out. } - void expectSnackBarNotVisibleError(WidgetTester tester) { - final AssertionError exception = tester.takeException() as AssertionError; - const String message = 'Floating SnackBar presented off screen.\n' - 'A SnackBar with behavior property set to SnackBarBehavior.floating is fully ' - 'or partially off screen because some or all the widgets provided to ' - 'Scaffold.floatingActionButton, Scaffold.persistentFooterButtons and ' - 'Scaffold.bottomNavigationBar take up too much vertical space.\n' - 'Consider constraining the size of these widgets to allow room for the SnackBar to be visible.'; - expect(exception.message, message); - } + const String offScreenMessage = 'Floating SnackBar presented off screen.\n' + 'A SnackBar with behavior property set to SnackBarBehavior.floating is fully ' + 'or partially off screen because some or all the widgets provided to ' + 'Scaffold.floatingActionButton, Scaffold.persistentFooterButtons and ' + 'Scaffold.bottomNavigationBar take up too much vertical space.\n' + 'Consider constraining the size of these widgets to allow room for the SnackBar to be visible.'; testWidgets('Snackbar with SnackBarBehavior.floating will assert when offset too high by a large Scaffold.floatingActionButton', (WidgetTester tester) async { // Regression test for https://github.com/flutter/flutter/issues/84263 @@ -1891,12 +2180,14 @@ void main() { // Run with the Snackbar fully off screen. await boilerplate(fabHeight: spaceAboveSnackBar + mediumFabHeight * 2); await openFloatingSnackBar(tester); - expectSnackBarNotVisibleError(tester); + AssertionError exception = tester.takeException() as AssertionError; + expect(exception.message, offScreenMessage); // Run with the Snackbar partially off screen. await boilerplate(fabHeight: spaceAboveSnackBar + mediumFabHeight + 10); await openFloatingSnackBar(tester); - expectSnackBarNotVisibleError(tester); + exception = tester.takeException() as AssertionError; + expect(exception.message, offScreenMessage); // Run with the Snackbar fully visible right on the top of the screen. await boilerplate(fabHeight: spaceAboveSnackBar + mediumFabHeight); @@ -1904,7 +2195,7 @@ void main() { expect(tester.takeException(), isNull); }); - testWidgets('Snackbar with SnackBarBehavior.floating will assert when offset too high by a large Scaffold.persistentFooterButtons', (WidgetTester tester) async { + testWidgets('Material2 - Snackbar with SnackBarBehavior.floating will assert when offset too high by a large Scaffold.persistentFooterButtons', (WidgetTester tester) async { // Regression test for https://github.com/flutter/flutter/issues/84263 await tester.pumpWidget( MaterialApp( @@ -1917,10 +2208,36 @@ void main() { await openFloatingSnackBar(tester); await tester.pumpAndSettle(); // Have the SnackBar fully animate out. - expectSnackBarNotVisibleError(tester); + + final AssertionError exception = tester.takeException() as AssertionError; + expect(exception.message, offScreenMessage); }); - testWidgets('Snackbar with SnackBarBehavior.floating will assert when offset too high by a large Scaffold.bottomNavigationBar', (WidgetTester tester) async { + testWidgets('Material3 - Snackbar with SnackBarBehavior.floating will assert when offset too high by a large Scaffold.persistentFooterButtons', (WidgetTester tester) async { + // Regression test for https://github.com/flutter/flutter/issues/84263 + await tester.pumpWidget( + MaterialApp( + theme: ThemeData(useMaterial3: true), + home: const Scaffold( + persistentFooterButtons: [SizedBox(height: 1000)], + ), + ), + ); + + final FlutterExceptionHandler? handler = FlutterError.onError; + final List errorMessages = []; + FlutterError.onError = (FlutterErrorDetails details) { + errorMessages.add(details.exceptionAsString()); + }; + addTearDown(() => FlutterError.onError = handler); + + await openFloatingSnackBar(tester); + await tester.pumpAndSettle(); // Have the SnackBar fully animate out. + + expect(errorMessages.contains(offScreenMessage), isTrue); + }); + + testWidgets('Material2 - Snackbar with SnackBarBehavior.floating will assert when offset too high by a large Scaffold.bottomNavigationBar', (WidgetTester tester) async { // Regression test for https://github.com/flutter/flutter/issues/84263 await tester.pumpWidget( MaterialApp( @@ -1933,7 +2250,32 @@ void main() { await openFloatingSnackBar(tester); await tester.pumpAndSettle(); // Have the SnackBar fully animate out. - expectSnackBarNotVisibleError(tester); + final AssertionError exception = tester.takeException() as AssertionError; + expect(exception.message, offScreenMessage); + }); + + testWidgets('Material3 - Snackbar with SnackBarBehavior.floating will assert when offset too high by a large Scaffold.bottomNavigationBar', (WidgetTester tester) async { + // Regression test for https://github.com/flutter/flutter/issues/84263 + await tester.pumpWidget( + MaterialApp( + theme: ThemeData(useMaterial3: true), + home: const Scaffold( + bottomNavigationBar: SizedBox(height: 1000), + ), + ), + ); + + final FlutterExceptionHandler? handler = FlutterError.onError; + final List errorMessages = []; + FlutterError.onError = (FlutterErrorDetails details) { + errorMessages.add(details.exceptionAsString()); + }; + addTearDown(() => FlutterError.onError = handler); + + await openFloatingSnackBar(tester); + await tester.pumpAndSettle(); // Have the SnackBar fully animate out. + + expect(errorMessages.contains(offScreenMessage), isTrue); }); testWidgets( @@ -2155,7 +2497,7 @@ void main() { expect(find.text(snackBarText), findsOneWidget); }); - testWidgets('SnackBars should be shown above the bottomSheet', (WidgetTester tester) async { + testWidgets('Material2 - SnackBars should be shown above the bottomSheet', (WidgetTester tester) async { await tester.pumpWidget(MaterialApp( theme: ThemeData(useMaterial3: false), home: const Scaffold( @@ -2178,7 +2520,33 @@ void main() { )); await tester.pumpAndSettle(); // Have the SnackBar fully animate out. - await expectLater(find.byType(MaterialApp), matchesGoldenFile('snack_bar.goldenTest.workWithBottomSheet.png')); + await expectLater(find.byType(MaterialApp), matchesGoldenFile('m2_snack_bar.goldenTest.workWithBottomSheet.png')); + }); + + testWidgets('Material3 - SnackBars should be shown above the bottomSheet', (WidgetTester tester) async { + await tester.pumpWidget(MaterialApp( + theme: ThemeData(useMaterial3: true), + home: const Scaffold( + bottomSheet: SizedBox( + width: 200, + height: 50, + child: ColoredBox( + color: Colors.pink, + ), + ), + ), + )); + + final ScaffoldMessengerState scaffoldMessengerState = tester.state(find.byType(ScaffoldMessenger)); + scaffoldMessengerState.showSnackBar(SnackBar( + content: const Text('I love Flutter!'), + duration: const Duration(seconds: 2), + action: SnackBarAction(label: 'ACTION', onPressed: () {}), + behavior: SnackBarBehavior.floating, + )); + await tester.pumpAndSettle(); // Have the SnackBar fully animate out. + + await expectLater(find.byType(MaterialApp), matchesGoldenFile('m3_snack_bar.goldenTest.workWithBottomSheet.png')); }); testWidgets('ScaffoldMessenger does not duplicate a SnackBar when presenting a MaterialBanner.', (WidgetTester tester) async { @@ -2238,7 +2606,7 @@ void main() { expect(find.text(materialBannerText), findsOneWidget); }); - testWidgets('ScaffoldMessenger presents SnackBars to only the root Scaffold when Scaffolds are nested.', (WidgetTester tester) async { + testWidgets('Material2 - ScaffoldMessenger presents SnackBars to only the root Scaffold when Scaffolds are nested.', (WidgetTester tester) async { await tester.pumpWidget(MaterialApp( theme: ThemeData(useMaterial3: false), home: Scaffold( @@ -2265,12 +2633,48 @@ void main() { // overlapping the FAB. await expectLater( find.byType(MaterialApp), - matchesGoldenFile('snack_bar.scaffold.nested.png'), + matchesGoldenFile('m2_snack_bar.scaffold.nested.png'), ); final Offset snackBarTopRight = tester.getTopRight(find.byType(SnackBar)); expect(snackBarTopRight.dy, 465.0); }); + testWidgets('Material3 - ScaffoldMessenger presents SnackBars to only the root Scaffold when Scaffolds are nested.', (WidgetTester tester) async { + await tester.pumpWidget(MaterialApp( + theme: ThemeData(useMaterial3: true), + home: Scaffold( + body: const Scaffold(), + floatingActionButton: FloatingActionButton(onPressed: () {}), + ), + )); + + 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, + )); + await tester.pumpAndSettle(); + + expect(find.byType(SnackBar), findsOneWidget); + // The FloatingActionButton helps us identify which Scaffold has the + // SnackBar here. Since the outer Scaffold contains a FAB, the SnackBar + // should be above it. If the inner Scaffold had the SnackBar, it would be + // overlapping the FAB. + await expectLater(find.byType(MaterialApp), matchesGoldenFile('m3_snack_bar.scaffold.nested.png')); + final Offset snackBarTopRight = tester.getTopRight(find.byType(SnackBar)); + + // TODO(bleroux): https://github.com/flutter/flutter/issues/99933 + // A bug in the HTML renderer and/or Chrome 96+ causes a + // discrepancy in the paragraph height. + const bool hasIssue99933 = kIsWeb && !bool.fromEnvironment('FLUTTER_WEB_USE_SKIA'); + expect(snackBarTopRight.dy, hasIssue99933 ? 464.0 : 465.0); + }); + + testWidgets('ScaffoldMessengerState clearSnackBars works as expected', (WidgetTester tester) async { final List snackBars = ['Hello Snackbar', 'Hi Snackbar', 'Bye Snackbar']; int snackBarCounter = 0; @@ -2438,8 +2842,7 @@ void main() { }); } - - testWidgets('Snackbar by default clips BackdropFilter', (WidgetTester tester) async { + testWidgets('Material2 - Snackbar by default clips BackdropFilter', (WidgetTester tester) async { // Regression test for https://github.com/flutter/flutter/issues/98205 await tester.pumpWidget(MaterialApp( theme: ThemeData(useMaterial3: false), @@ -2469,7 +2872,40 @@ void main() { await tester.tap(find.text('I am a snack bar.')); await tester.pump(); // start animation await tester.pump(const Duration(milliseconds: 750)); - await expectLater(find.byType(MaterialApp), matchesGoldenFile('snack_bar.goldenTest.backdropFilter.png')); + await expectLater(find.byType(MaterialApp), matchesGoldenFile('m2_snack_bar.goldenTest.backdropFilter.png')); + }); + + testWidgets('Material3 - Snackbar by default clips BackdropFilter', (WidgetTester tester) async { + // Regression test for https://github.com/flutter/flutter/issues/98205 + await tester.pumpWidget(MaterialApp( + theme: ThemeData(useMaterial3: true), + home: Scaffold( + body: const Scaffold(), + floatingActionButton: FloatingActionButton(onPressed: () {}), + ), + )); + + final ScaffoldMessengerState scaffoldMessengerState = tester.state( + find.byType(ScaffoldMessenger), + ); + scaffoldMessengerState.showSnackBar(SnackBar( + backgroundColor: Colors.transparent, + content: BackdropFilter( + filter: ImageFilter.blur( + sigmaX: 20.0, + sigmaY: 20.0, + ), + child: const Text('I am a snack bar.'), + ), + duration: const Duration(seconds: 2), + action: SnackBarAction(label: 'ACTION', onPressed: () {}), + behavior: SnackBarBehavior.fixed, + )); + await tester.pumpAndSettle(); + await tester.tap(find.text('I am a snack bar.')); + await tester.pump(); // start animation + await tester.pump(const Duration(milliseconds: 750)); + await expectLater(find.byType(MaterialApp), matchesGoldenFile('m3_snack_bar.goldenTest.backdropFilter.png')); }); testWidgets('Floating snackbar can display optional icon', (WidgetTester tester) async { @@ -2504,7 +2940,7 @@ void main() { 'snack_bar.goldenTest.floatingWithActionWithIcon.png')); }); - testWidgets('Fixed width snackbar can display optional icon', (WidgetTester tester) async { + testWidgets('Material2 - Fixed width snackbar can display optional icon', (WidgetTester tester) async { await tester.pumpWidget(MaterialApp( theme: ThemeData(useMaterial3: false), home: const Scaffold( @@ -2528,10 +2964,37 @@ void main() { )); await tester.pumpAndSettle(); // Have the SnackBar fully animate out. - await expectLater(find.byType(MaterialApp), matchesGoldenFile('snack_bar.goldenTest.fixedWithActionWithIcon.png')); + await expectLater(find.byType(MaterialApp), matchesGoldenFile('m2_snack_bar.goldenTest.fixedWithActionWithIcon.png')); }); - testWidgets('Fixed snackbar can display optional icon without action', (WidgetTester tester) async { + testWidgets('Material3 - Fixed width snackbar can display optional icon', (WidgetTester tester) async { + await tester.pumpWidget(MaterialApp( + theme: ThemeData(useMaterial3: true), + home: const Scaffold( + bottomSheet: SizedBox( + width: 200, + height: 50, + child: ColoredBox( + color: Colors.pink, + ), + ), + ), + )); + + final ScaffoldMessengerState scaffoldMessengerState = tester.state(find.byType(ScaffoldMessenger)); + scaffoldMessengerState.showSnackBar(SnackBar( + content: const Text('Go get a snack'), + duration: const Duration(seconds: 2), + action: SnackBarAction(label: 'ACTION', onPressed: () {}), + showCloseIcon: true, + behavior: SnackBarBehavior.fixed, + )); + await tester.pumpAndSettle(); // Have the SnackBar fully animate out. + + await expectLater(find.byType(MaterialApp), matchesGoldenFile('m3_snack_bar.goldenTest.fixedWithActionWithIcon.png')); + }); + + testWidgets('Material2 - Fixed snackbar can display optional icon without action', (WidgetTester tester) async { await tester.pumpWidget(MaterialApp( theme: ThemeData(useMaterial3: false), home: const Scaffold( @@ -2556,11 +3019,38 @@ void main() { ); await tester.pumpAndSettle(); // Have the SnackBar fully animate out. - await expectLater(find.byType(MaterialApp), matchesGoldenFile('snack_bar.goldenTest.fixedWithIcon.png')); + await expectLater(find.byType(MaterialApp), matchesGoldenFile('m2_snack_bar.goldenTest.fixedWithIcon.png')); }); - testWidgets( - 'Floating width snackbar can display optional icon without action', (WidgetTester tester) async { + testWidgets('Material3 - Fixed snackbar can display optional icon without action', (WidgetTester tester) async { + await tester.pumpWidget(MaterialApp( + theme: ThemeData(useMaterial3: true), + home: const Scaffold( + bottomSheet: SizedBox( + width: 200, + height: 50, + child: ColoredBox( + color: Colors.pink, + ), + ), + ), + )); + + final ScaffoldMessengerState scaffoldMessengerState = tester.state(find.byType(ScaffoldMessenger)); + scaffoldMessengerState.showSnackBar( + const SnackBar( + content: Text('I wonder if there are snacks nearby?'), + duration: Duration(seconds: 2), + behavior: SnackBarBehavior.fixed, + showCloseIcon: true, + ), + ); + await tester.pumpAndSettle(); // Have the SnackBar fully animate out. + + await expectLater(find.byType(MaterialApp), matchesGoldenFile('m3_snack_bar.goldenTest.fixedWithIcon.png')); + }); + + testWidgets('Material2 - Floating width snackbar can display optional icon without action', (WidgetTester tester) async { await tester.pumpWidget(MaterialApp( theme: ThemeData(useMaterial3: false), home: const Scaffold( @@ -2583,11 +3073,36 @@ void main() { )); await tester.pumpAndSettle(); // Have the SnackBar fully animate out. - await expectLater(find.byType(MaterialApp), - matchesGoldenFile('snack_bar.goldenTest.floatingWithIcon.png')); + await expectLater(find.byType(MaterialApp), matchesGoldenFile('m2_snack_bar.goldenTest.floatingWithIcon.png')); }); - testWidgets('Floating multi-line snackbar with icon is aligned correctly', (WidgetTester tester) async { + testWidgets('Material3 - Floating width snackbar can display optional icon without action', (WidgetTester tester) async { + await tester.pumpWidget(MaterialApp( + theme: ThemeData(useMaterial3: true), + home: const Scaffold( + bottomSheet: SizedBox( + width: 200, + height: 50, + child: ColoredBox( + color: Colors.pink, + ), + ), + ), + )); + + final ScaffoldMessengerState scaffoldMessengerState = tester.state(find.byType(ScaffoldMessenger)); + scaffoldMessengerState.showSnackBar(const SnackBar( + content: Text('Must go get a snack!'), + duration: Duration(seconds: 2), + showCloseIcon: true, + behavior: SnackBarBehavior.floating, + )); + await tester.pumpAndSettle(); // Have the SnackBar fully animate out. + + await expectLater(find.byType(MaterialApp), matchesGoldenFile('m3_snack_bar.goldenTest.floatingWithIcon.png')); + }); + + testWidgets('Material2 - Floating multi-line snackbar with icon is aligned correctly', (WidgetTester tester) async { await tester.pumpWidget(MaterialApp( theme: ThemeData(useMaterial3: false), home: const Scaffold( @@ -2611,11 +3126,37 @@ void main() { )); await tester.pumpAndSettle(); // Have the SnackBar fully animate out. - await expectLater(find.byType(MaterialApp), - matchesGoldenFile('snack_bar.goldenTest.multiLineWithIcon.png')); + await expectLater(find.byType(MaterialApp), matchesGoldenFile('m2_snack_bar.goldenTest.multiLineWithIcon.png')); }); - testWidgets('Floating multi-line snackbar with icon and actionOverflowThreshold=1 is aligned correctly', (WidgetTester tester) async { + testWidgets('Material3 - Floating multi-line snackbar with icon is aligned correctly', (WidgetTester tester) async { + await tester.pumpWidget(MaterialApp( + theme: ThemeData(useMaterial3: true), + home: const Scaffold( + bottomSheet: SizedBox( + width: 200, + height: 50, + child: ColoredBox( + color: Colors.pink, + ), + ), + ), + )); + + final ScaffoldMessengerState scaffoldMessengerState = tester.state(find.byType(ScaffoldMessenger)); + scaffoldMessengerState.showSnackBar(const SnackBar( + content: Text( + 'This is a really long snackbar message. So long, it spans across more than one line!'), + duration: Duration(seconds: 2), + showCloseIcon: true, + behavior: SnackBarBehavior.floating, + )); + await tester.pumpAndSettle(); // Have the SnackBar fully animate out. + + await expectLater(find.byType(MaterialApp), matchesGoldenFile('m3_snack_bar.goldenTest.multiLineWithIcon.png')); + }); + + testWidgets('Material2 - Floating multi-line snackbar with icon and actionOverflowThreshold=1 is aligned correctly', (WidgetTester tester) async { await tester.pumpWidget(MaterialApp( theme: ThemeData(useMaterial3: false), home: const Scaffold( @@ -2639,8 +3180,40 @@ void main() { )); await tester.pumpAndSettle(); // Have the SnackBar fully animate in. - await expectLater(find.byType(MaterialApp), - matchesGoldenFile('snack_bar.goldenTest.multiLineWithIconWithZeroActionOverflowThreshold.png')); + await expectLater( + find.byType(MaterialApp), + matchesGoldenFile('m2_snack_bar.goldenTest.multiLineWithIconWithZeroActionOverflowThreshold.png'), + ); + }); + + testWidgets('Material3 - Floating multi-line snackbar with icon and actionOverflowThreshold=1 is aligned correctly', (WidgetTester tester) async { + await tester.pumpWidget(MaterialApp( + theme: ThemeData(useMaterial3: true), + home: const Scaffold( + bottomSheet: SizedBox( + width: 200, + height: 50, + child: ColoredBox( + color: Colors.pink, + ), + ), + ), + )); + + final ScaffoldMessengerState scaffoldMessengerState = tester.state(find.byType(ScaffoldMessenger)); + scaffoldMessengerState.showSnackBar(const SnackBar( + content: Text('This is a really long snackbar message. So long, it spans across more than one line!'), + duration: Duration(seconds: 2), + showCloseIcon: true, + behavior: SnackBarBehavior.floating, + actionOverflowThreshold: 1, + )); + await tester.pumpAndSettle(); // Have the SnackBar fully animate in. + + await expectLater( + find.byType(MaterialApp), + matchesGoldenFile('m3_snack_bar.goldenTest.multiLineWithIconWithZeroActionOverflowThreshold.png'), + ); }); testWidgets(