diff --git a/packages/flutter/lib/src/material/elevated_button.dart b/packages/flutter/lib/src/material/elevated_button.dart index 1f5c362375..699e77035c 100644 --- a/packages/flutter/lib/src/material/elevated_button.dart +++ b/packages/flutter/lib/src/material/elevated_button.dart @@ -156,9 +156,11 @@ class ElevatedButton extends ButtonStyleButton { /// used to create a [WidgetStateProperty] [ButtonStyle.backgroundColor]. /// /// Similarly, the [enabledMouseCursor] and [disabledMouseCursor] - /// parameters are used to construct [ButtonStyle.mouseCursor] and - /// [iconColor], [disabledIconColor] are used to construct - /// [ButtonStyle.iconColor]. + /// parameters are used to construct [ButtonStyle.mouseCursor]. + /// + /// The [iconColor], [disabledIconColor] are used to construct + /// [ButtonStyle.iconColor] and [iconSize] is used to construct + /// [ButtonStyle.iconSize]. /// /// The button's elevations are defined relative to the [elevation] /// parameter. The disabled elevation is the same as the parameter @@ -207,6 +209,7 @@ class ElevatedButton extends ButtonStyleButton { Color? shadowColor, Color? surfaceTintColor, Color? iconColor, + double? iconSize, Color? disabledIconColor, Color? overlayColor, double? elevation, @@ -261,6 +264,7 @@ class ElevatedButton extends ButtonStyleButton { shadowColor: ButtonStyleButton.allOrNull(shadowColor), surfaceTintColor: ButtonStyleButton.allOrNull(surfaceTintColor), iconColor: ButtonStyleButton.defaultColor(iconColor, disabledIconColor), + iconSize: ButtonStyleButton.allOrNull(iconSize), elevation: elevationValue, padding: ButtonStyleButton.allOrNull(padding), minimumSize: ButtonStyleButton.allOrNull(minimumSize), diff --git a/packages/flutter/lib/src/material/filled_button.dart b/packages/flutter/lib/src/material/filled_button.dart index 910d4cc846..bfb5bbce73 100644 --- a/packages/flutter/lib/src/material/filled_button.dart +++ b/packages/flutter/lib/src/material/filled_button.dart @@ -229,6 +229,10 @@ class FilledButton extends ButtonStyleButton { /// Similarly, the [enabledMouseCursor] and [disabledMouseCursor] /// parameters are used to construct [ButtonStyle.mouseCursor]. /// + /// The [iconColor], [disabledIconColor] are used to construct + /// [ButtonStyle.iconColor] and [iconSize] is used to construct + /// [ButtonStyle.iconSize]. + /// /// The button's elevations are defined relative to the [elevation] /// parameter. The disabled elevation is the same as the parameter /// value, [elevation] + 2 is used when the button is hovered @@ -270,6 +274,7 @@ class FilledButton extends ButtonStyleButton { Color? shadowColor, Color? surfaceTintColor, Color? iconColor, + double? iconSize, Color? disabledIconColor, Color? overlayColor, double? elevation, @@ -311,6 +316,7 @@ class FilledButton extends ButtonStyleButton { shadowColor: ButtonStyleButton.allOrNull(shadowColor), surfaceTintColor: ButtonStyleButton.allOrNull(surfaceTintColor), iconColor: ButtonStyleButton.defaultColor(iconColor, disabledIconColor), + iconSize: ButtonStyleButton.allOrNull(iconSize), elevation: ButtonStyleButton.allOrNull(elevation), padding: ButtonStyleButton.allOrNull(padding), minimumSize: ButtonStyleButton.allOrNull(minimumSize), diff --git a/packages/flutter/lib/src/material/menu_anchor.dart b/packages/flutter/lib/src/material/menu_anchor.dart index 087852547d..f5e118cdac 100644 --- a/packages/flutter/lib/src/material/menu_anchor.dart +++ b/packages/flutter/lib/src/material/menu_anchor.dart @@ -996,6 +996,13 @@ class MenuItemButton extends StatefulWidget { /// [disabledBackgroundColor] to specify the button's disabled icon and fill /// color. /// + /// Similarly, the [enabledMouseCursor] and [disabledMouseCursor] + /// parameters are used to construct [ButtonStyle.mouseCursor]. + /// + /// The [iconColor], [disabledIconColor] are used to construct + /// [ButtonStyle.iconColor] and [iconSize] is used to construct + /// [ButtonStyle.iconSize]. + /// /// All of the other parameters are either used directly or used to create a /// [WidgetStateProperty] with a single value for all states. /// @@ -1025,6 +1032,8 @@ class MenuItemButton extends StatefulWidget { Color? shadowColor, Color? surfaceTintColor, Color? iconColor, + double? iconSize, + Color? disabledIconColor, TextStyle? textStyle, Color? overlayColor, double? elevation, @@ -1051,6 +1060,8 @@ class MenuItemButton extends StatefulWidget { shadowColor: shadowColor, surfaceTintColor: surfaceTintColor, iconColor: iconColor, + iconSize: iconSize, + disabledIconColor: disabledIconColor, textStyle: textStyle, overlayColor: overlayColor, elevation: elevation, @@ -1777,6 +1788,13 @@ class SubmenuButton extends StatefulWidget { /// [disabledBackgroundColor] to specify the button's disabled icon and fill /// color. /// + /// Similarly, the [enabledMouseCursor] and [disabledMouseCursor] + /// parameters are used to construct [ButtonStyle.mouseCursor]. + /// + /// The [iconColor], [disabledIconColor] are used to construct + /// [ButtonStyle.iconColor] and [iconSize] is used to construct + /// [ButtonStyle.iconSize]. + /// /// All of the other parameters are either used directly or used to create a /// [WidgetStateProperty] with a single value for all states. /// @@ -1804,6 +1822,8 @@ class SubmenuButton extends StatefulWidget { Color? shadowColor, Color? surfaceTintColor, Color? iconColor, + double? iconSize, + Color? disabledIconColor, TextStyle? textStyle, Color? overlayColor, double? elevation, @@ -1830,6 +1850,8 @@ class SubmenuButton extends StatefulWidget { shadowColor: shadowColor, surfaceTintColor: surfaceTintColor, iconColor: iconColor, + disabledIconColor: disabledIconColor, + iconSize: iconSize, textStyle: textStyle, overlayColor: overlayColor, elevation: elevation, diff --git a/packages/flutter/lib/src/material/outlined_button.dart b/packages/flutter/lib/src/material/outlined_button.dart index 93f523f71c..d35af8c528 100644 --- a/packages/flutter/lib/src/material/outlined_button.dart +++ b/packages/flutter/lib/src/material/outlined_button.dart @@ -155,9 +155,11 @@ class OutlinedButton extends ButtonStyleButton { /// used to create a [WidgetStateProperty] [ButtonStyle.backgroundColor]. /// /// Similarly, the [enabledMouseCursor] and [disabledMouseCursor] - /// parameters are used to construct [ButtonStyle.mouseCursor] and - /// [iconColor], [disabledIconColor] are used to construct - /// [ButtonStyle.iconColor]. + /// parameters are used to construct [ButtonStyle.mouseCursor]. + /// + /// The [iconColor], [disabledIconColor] are used to construct + /// [ButtonStyle.iconColor] and [iconSize] is used to construct + /// [ButtonStyle.iconSize]. /// /// If [overlayColor] is specified and its value is [Colors.transparent] /// then the pressed/focused/hovered highlights are effectively defeated. @@ -194,6 +196,7 @@ class OutlinedButton extends ButtonStyleButton { Color? shadowColor, Color? surfaceTintColor, Color? iconColor, + double? iconSize, Color? disabledIconColor, Color? overlayColor, double? elevation, @@ -239,6 +242,7 @@ class OutlinedButton extends ButtonStyleButton { shadowColor: ButtonStyleButton.allOrNull(shadowColor), surfaceTintColor: ButtonStyleButton.allOrNull(surfaceTintColor), iconColor: ButtonStyleButton.defaultColor(iconColor, disabledIconColor), + iconSize: ButtonStyleButton.allOrNull(iconSize), elevation: ButtonStyleButton.allOrNull(elevation), padding: ButtonStyleButton.allOrNull(padding), minimumSize: ButtonStyleButton.allOrNull(minimumSize), diff --git a/packages/flutter/lib/src/material/segmented_button.dart b/packages/flutter/lib/src/material/segmented_button.dart index fefb1a3f31..322f3c1497 100644 --- a/packages/flutter/lib/src/material/segmented_button.dart +++ b/packages/flutter/lib/src/material/segmented_button.dart @@ -232,6 +232,10 @@ class SegmentedButton extends StatefulWidget { /// Similarly, the [enabledMouseCursor] and [disabledMouseCursor] /// parameters are used to construct [ButtonStyle.mouseCursor]. /// + /// The [iconColor], [disabledIconColor] are used to construct + /// [ButtonStyle.iconColor] and [iconSize] is used to construct + /// [ButtonStyle.iconSize]. + /// /// All of the other parameters are either used directly or used to /// create a [WidgetStateProperty] with a single value for all /// states. @@ -282,6 +286,9 @@ class SegmentedButton extends StatefulWidget { Color? disabledBackgroundColor, Color? shadowColor, Color? surfaceTintColor, + Color? iconColor, + double? iconSize, + Color? disabledIconColor, Color? overlayColor, double? elevation, TextStyle? textStyle, @@ -311,6 +318,9 @@ class SegmentedButton extends StatefulWidget { textStyle: textStyle, shadowColor: shadowColor, surfaceTintColor: surfaceTintColor, + iconColor: iconColor, + iconSize: iconSize, + disabledIconColor: disabledIconColor, elevation: elevation, padding: padding, minimumSize: minimumSize, diff --git a/packages/flutter/lib/src/material/text_button.dart b/packages/flutter/lib/src/material/text_button.dart index 04590e82e2..39cb598443 100644 --- a/packages/flutter/lib/src/material/text_button.dart +++ b/packages/flutter/lib/src/material/text_button.dart @@ -163,9 +163,11 @@ class TextButton extends ButtonStyleButton { /// used to create a [WidgetStateProperty] [ButtonStyle.backgroundColor]. /// /// Similarly, the [enabledMouseCursor] and [disabledMouseCursor] - /// parameters are used to construct [ButtonStyle.mouseCursor] and - /// [iconColor], [disabledIconColor] are used to construct - /// [ButtonStyle.iconColor]. + /// parameters are used to construct [ButtonStyle.mouseCursor]. + /// + /// The [iconColor], [disabledIconColor] are used to construct + /// [ButtonStyle.iconColor] and [iconSize] is used to construct + /// [ButtonStyle.iconSize]. /// /// If [overlayColor] is specified and its value is [Colors.transparent] /// then the pressed/focused/hovered highlights are effectively defeated. @@ -201,6 +203,7 @@ class TextButton extends ButtonStyleButton { Color? shadowColor, Color? surfaceTintColor, Color? iconColor, + double? iconSize, Color? disabledIconColor, Color? overlayColor, double? elevation, @@ -250,6 +253,7 @@ class TextButton extends ButtonStyleButton { shadowColor: ButtonStyleButton.allOrNull(shadowColor), surfaceTintColor: ButtonStyleButton.allOrNull(surfaceTintColor), iconColor: iconColorProp, + iconSize: ButtonStyleButton.allOrNull(iconSize), elevation: ButtonStyleButton.allOrNull(elevation), padding: ButtonStyleButton.allOrNull(padding), minimumSize: ButtonStyleButton.allOrNull(minimumSize), diff --git a/packages/flutter/test/material/elevated_button_test.dart b/packages/flutter/test/material/elevated_button_test.dart index 65c9b63b48..3523ca5d42 100644 --- a/packages/flutter/test/material/elevated_button_test.dart +++ b/packages/flutter/test/material/elevated_button_test.dart @@ -10,6 +10,13 @@ import 'package:flutter_test/flutter_test.dart'; import '../widgets/semantics_tester.dart'; void main() { + TextStyle iconStyle(WidgetTester tester, IconData icon) { + final RichText iconRichText = tester.widget( + find.descendant(of: find.byIcon(icon), matching: find.byType(RichText)), + ); + return iconRichText.text.style!; + } + testWidgets('ElevatedButton, ElevatedButton.icon defaults', (WidgetTester tester) async { const ColorScheme colorScheme = ColorScheme.light(); final ThemeData theme = ThemeData.from(colorScheme: colorScheme); @@ -537,14 +544,13 @@ void main() { ), ); - Color iconColor() => _iconStyle(tester, Icons.add).color!; // Default, not disabled. - expect(iconColor(), equals(defaultColor)); + expect(iconStyle(tester, Icons.add).color, equals(defaultColor)); // Focused. focusNode.requestFocus(); await tester.pumpAndSettle(); - expect(iconColor(), focusedColor); + expect(iconStyle(tester, Icons.add).color, focusedColor); // Hovered. final Offset center = tester.getCenter(find.byKey(buttonKey)); @@ -554,13 +560,13 @@ void main() { await gesture.addPointer(); await gesture.moveTo(center); await tester.pumpAndSettle(); - expect(iconColor(), hoverColor); + expect(iconStyle(tester, Icons.add).color, hoverColor); // Highlighted (pressed). await gesture.down(center); await tester.pump(); // Start the splash and highlight animations. await tester.pump(const Duration(milliseconds: 800)); // Wait for splash and highlight to be well under way. - expect(iconColor(), pressedColor); + expect(iconStyle(tester, Icons.add).color, pressedColor); focusNode.dispose(); }); @@ -2400,11 +2406,36 @@ void main() { // The icon is aligned to the left of the button. expect(buttonTopLeft.dx, iconTopLeft.dx - 24.0); // 24.0 - padding between icon and button edge. }); -} -TextStyle _iconStyle(WidgetTester tester, IconData icon) { - final RichText iconRichText = tester.widget( - find.descendant(of: find.byIcon(icon), matching: find.byType(RichText)), - ); - return iconRichText.text.style!; + // Regression test for https://github.com/flutter/flutter/issues/154798. + testWidgets('ElevatedButton.styleFrom can customize the button icon', (WidgetTester tester) async { + const Color iconColor = Color(0xFFF000FF); + const double iconSize = 32.0; + const Color disabledIconColor = Color(0xFFFFF000); + Widget buildButton({ bool enabled = true }) { + return MaterialApp( + home: Material( + child: ElevatedButton.icon( + style: ElevatedButton.styleFrom( + iconColor: iconColor, + iconSize: iconSize, + disabledIconColor: disabledIconColor, + ), + onPressed: enabled ? () {} : null, + icon: const Icon(Icons.add), + label: const Text('Button'), + ), + ), + ); + } + + // Test enabled button. + await tester.pumpWidget(buildButton()); + expect(tester.getSize(find.byIcon(Icons.add)), const Size(iconSize, iconSize)); + expect(iconStyle(tester, Icons.add).color, iconColor); + + // Test disabled button. + await tester.pumpWidget(buildButton(enabled: false)); + expect(iconStyle(tester, Icons.add).color, disabledIconColor); + }); } diff --git a/packages/flutter/test/material/filled_button_test.dart b/packages/flutter/test/material/filled_button_test.dart index 5d8b559efb..6ce01c6941 100644 --- a/packages/flutter/test/material/filled_button_test.dart +++ b/packages/flutter/test/material/filled_button_test.dart @@ -10,6 +10,13 @@ import 'package:flutter_test/flutter_test.dart'; import '../widgets/semantics_tester.dart'; void main() { + TextStyle iconStyle(WidgetTester tester, IconData icon) { + final RichText iconRichText = tester.widget( + find.descendant(of: find.byIcon(icon), matching: find.byType(RichText)), + ); + return iconRichText.text.style!; + } + testWidgets('FilledButton, FilledButton.icon defaults', (WidgetTester tester) async { const ColorScheme colorScheme = ColorScheme.light(); final ThemeData theme = ThemeData.from(useMaterial3: false, colorScheme: colorScheme); @@ -729,14 +736,13 @@ void main() { ), ); - Color iconColor() => _iconStyle(tester, Icons.add).color!; // Default, not disabled. - expect(iconColor(), equals(defaultColor)); + expect(iconStyle(tester, Icons.add).color, equals(defaultColor)); // Focused. focusNode.requestFocus(); await tester.pumpAndSettle(); - expect(iconColor(), focusedColor); + expect(iconStyle(tester, Icons.add).color, focusedColor); // Hovered. final Offset center = tester.getCenter(find.byKey(buttonKey)); @@ -746,13 +752,13 @@ void main() { await gesture.addPointer(); await gesture.moveTo(center); await tester.pumpAndSettle(); - expect(iconColor(), hoverColor); + expect(iconStyle(tester, Icons.add).color, hoverColor); // Highlighted (pressed). await gesture.down(center); await tester.pump(); // Start the splash and highlight animations. await tester.pump(const Duration(milliseconds: 800)); // Wait for splash and highlight to be well under way. - expect(iconColor(), pressedColor); + expect(iconStyle(tester, Icons.add).color, pressedColor); focusNode.dispose(); }); @@ -2625,11 +2631,38 @@ void main() { // The icon is aligned to the left of the button. expect(buttonTopLeft.dx, iconTopLeft.dx - 24.0); // 24.0 - padding between icon and button edge. }); -} -TextStyle _iconStyle(WidgetTester tester, IconData icon) { - final RichText iconRichText = tester.widget( - find.descendant(of: find.byIcon(icon), matching: find.byType(RichText)), - ); - return iconRichText.text.style!; + // Regression test for https://github.com/flutter/flutter/issues/154798. + testWidgets('FilledButton.styleFrom can customize the button icon', (WidgetTester tester) async { + const Color iconColor = Color(0xFFF000FF); + const double iconSize = 32.0; + const Color disabledIconColor = Color(0xFFFFF000); + Widget buildButton({ bool enabled = true }) { + return MaterialApp( + home: Material( + child: Center( + child: FilledButton.icon( + style: FilledButton.styleFrom( + iconColor: iconColor, + iconSize: iconSize, + disabledIconColor: disabledIconColor, + ), + onPressed: enabled ? () {} : null, + icon: const Icon(Icons.add), + label: const Text('Button'), + ), + ), + ), + ); + } + + // Test enabled button. + await tester.pumpWidget(buildButton()); + expect(tester.getSize(find.byIcon(Icons.add)), const Size(iconSize, iconSize)); + expect(iconStyle(tester, Icons.add).color, iconColor); + + // Test disabled button. + await tester.pumpWidget(buildButton(enabled: false)); + expect(iconStyle(tester, Icons.add).color, disabledIconColor); + }); } diff --git a/packages/flutter/test/material/menu_anchor_test.dart b/packages/flutter/test/material/menu_anchor_test.dart index c88e68c317..5c1bf9981a 100644 --- a/packages/flutter/test/material/menu_anchor_test.dart +++ b/packages/flutter/test/material/menu_anchor_test.dart @@ -172,6 +172,13 @@ void main() { return tester.allRenderObjects.firstWhere((RenderObject object) => object.runtimeType.toString() == '_RenderInkFeatures'); } + TextStyle iconStyle(WidgetTester tester, IconData icon) { + final RichText iconRichText = tester.widget( + find.descendant(of: find.byIcon(icon), matching: find.byType(RichText)), + ); + return iconRichText.text.style!; + } + testWidgets('Menu responds to density changes', (WidgetTester tester) async { Widget buildMenu({VisualDensity? visualDensity = VisualDensity.standard}) { return MaterialApp( @@ -4508,6 +4515,77 @@ void main() { expect(state.target, isNull); }, skip: kIsWeb // [intended] ForceGC does not work in web and in release mode. See https://api.flutter.dev/flutter/package-leak_tracker_leak_tracker/forceGC.html ); + + // Regression test for https://github.com/flutter/flutter/issues/154798. + testWidgets('MenuItemButton.styleFrom can customize the button icon', (WidgetTester tester) async { + const Color iconColor = Color(0xFFF000FF); + const double iconSize = 32.0; + const Color disabledIconColor = Color(0xFFFFF000); + Widget buildButton({ bool enabled = true }) { + return MaterialApp( + home: Material( + child: Center( + child: MenuItemButton( + style: MenuItemButton.styleFrom( + iconColor: iconColor, + iconSize: iconSize, + disabledIconColor: disabledIconColor, + ), + onPressed: enabled ? () {} : null, + trailingIcon: const Icon(Icons.add), + child: const Text('Button'), + ), + ), + ), + ); + } + + // Test enabled button. + await tester.pumpWidget(buildButton()); + expect(tester.getSize(find.byIcon(Icons.add)), const Size(iconSize, iconSize)); + expect(iconStyle(tester, Icons.add).color, iconColor); + + // Test disabled button. + await tester.pumpWidget(buildButton(enabled: false)); + expect(iconStyle(tester, Icons.add).color, disabledIconColor); + }); + + // Regression test for https://github.com/flutter/flutter/issues/154798. + testWidgets('SubmenuButton.styleFrom can customize the button icon', (WidgetTester tester) async { + const Color iconColor = Color(0xFFF000FF); + const double iconSize = 32.0; + const Color disabledIconColor = Color(0xFFFFF000); + Widget buildButton({ bool enabled = true }) { + return MaterialApp( + home: Material( + child: Center( + child: SubmenuButton( + style: SubmenuButton.styleFrom( + iconColor: iconColor, + iconSize: iconSize, + disabledIconColor: disabledIconColor, + ), + trailingIcon: const Icon(Icons.add), + menuChildren: [ + if (enabled) + const Text('Item'), + ], + child: const Text('SubmenuButton'), + ), + ), + ), + ); + } + + // Test enabled button. + await tester.pumpWidget(buildButton()); + expect(tester.getSize(find.byIcon(Icons.add)), const Size(iconSize, iconSize)); + expect(iconStyle(tester, Icons.add).color, iconColor); + + // Test disabled button. + await tester.pumpWidget(buildButton(enabled: false)); + expect(iconStyle(tester, Icons.add).color, disabledIconColor); + }); } List createTestMenus({ diff --git a/packages/flutter/test/material/outlined_button_test.dart b/packages/flutter/test/material/outlined_button_test.dart index 48e80bc0e9..9fe25e976b 100644 --- a/packages/flutter/test/material/outlined_button_test.dart +++ b/packages/flutter/test/material/outlined_button_test.dart @@ -10,6 +10,13 @@ import 'package:flutter_test/flutter_test.dart'; import '../widgets/semantics_tester.dart'; void main() { + TextStyle iconStyle(WidgetTester tester, IconData icon) { + final RichText iconRichText = tester.widget( + find.descendant(of: find.byIcon(icon), matching: find.byType(RichText)), + ); + return iconRichText.text.style!; + } + testWidgets('OutlinedButton, OutlinedButton.icon defaults', (WidgetTester tester) async { const ColorScheme colorScheme = ColorScheme.light(); final ThemeData theme = ThemeData.from(colorScheme: colorScheme); @@ -720,14 +727,13 @@ void main() { ), ); - Color iconColor() => _iconStyle(tester, Icons.add).color!; // Default, not disabled. - expect(iconColor(), equals(defaultColor)); + expect(iconStyle(tester, Icons.add).color, equals(defaultColor)); // Focused. focusNode.requestFocus(); await tester.pumpAndSettle(); - expect(iconColor(), focusedColor); + expect(iconStyle(tester, Icons.add).color, focusedColor); // Hovered. final Offset center = tester.getCenter(find.byKey(buttonKey)); @@ -737,13 +743,13 @@ void main() { await gesture.addPointer(); await gesture.moveTo(center); await tester.pumpAndSettle(); - expect(iconColor(), hoverColor); + expect(iconStyle(tester, Icons.add).color, hoverColor); // Highlighted (pressed). await gesture.down(center); await tester.pump(); // Start the splash and highlight animations. await tester.pump(const Duration(milliseconds: 800)); // Wait for splash and highlight to be well under way. - expect(iconColor(), pressedColor); + expect(iconStyle(tester, Icons.add).color, pressedColor); focusNode.dispose(); }); @@ -2630,7 +2636,7 @@ void main() { focusNode.dispose(); }); - testWidgets('disabled and hovered OutlinedButton.icon responds to mouse-exit', (WidgetTester tester) async { + testWidgets('Disabled and hovered OutlinedButton.icon responds to mouse-exit', (WidgetTester tester) async { int onHoverCount = 0; late bool hover; const Key key = Key('OutlinedButton.icon'); @@ -2694,7 +2700,7 @@ void main() { expect(hover, false); }); - testWidgets('Can set OutlinedButton.icon focus and Can set unFocus.', (WidgetTester tester) async { + testWidgets('OutlinedButton.icon can be focused/unfocused', (WidgetTester tester) async { final FocusNode node = FocusNode(debugLabel: 'OutlinedButton.icon Focus'); bool gotFocus = false; await tester.pumpWidget( @@ -2721,7 +2727,7 @@ void main() { node.dispose(); }); - testWidgets('When OutlinedButton.icon disable, Can not set OutlinedButton.icon focus.', (WidgetTester tester) async { + testWidgets('Disabled OutlinedButton.icon cannot receive focus', (WidgetTester tester) async { final FocusNode node = FocusNode(debugLabel: 'OutlinedButton.icon Focus'); bool gotFocus = false; await tester.pumpWidget( @@ -2743,11 +2749,38 @@ void main() { expect(node.hasFocus, isFalse); node.dispose(); }); -} -TextStyle _iconStyle(WidgetTester tester, IconData icon) { - final RichText iconRichText = tester.widget( - find.descendant(of: find.byIcon(icon), matching: find.byType(RichText)), - ); - return iconRichText.text.style!; + // Regression test for https://github.com/flutter/flutter/issues/154798. + testWidgets('OutlinedButton.styleFrom can customize the button icon', (WidgetTester tester) async { + const Color iconColor = Color(0xFFF000FF); + const double iconSize = 32.0; + const Color disabledIconColor = Color(0xFFFFF000); + Widget buildButton({ bool enabled = true }) { + return MaterialApp( + home: Material( + child: Center( + child: OutlinedButton.icon( + style: OutlinedButton.styleFrom( + iconColor: iconColor, + iconSize: iconSize, + disabledIconColor: disabledIconColor, + ), + onPressed: enabled ? () {} : null, + icon: const Icon(Icons.add), + label: const Text('Button'), + ), + ), + ), + ); + } + + // Test enabled button. + await tester.pumpWidget(buildButton()); + expect(tester.getSize(find.byIcon(Icons.add)), const Size(iconSize, iconSize)); + expect(iconStyle(tester, Icons.add).color, iconColor); + + // Test disabled button. + await tester.pumpWidget(buildButton(enabled: false)); + expect(iconStyle(tester, Icons.add).color, disabledIconColor); + }); } diff --git a/packages/flutter/test/material/segmented_button_test.dart b/packages/flutter/test/material/segmented_button_test.dart index a7ba69f113..d05ee15fc0 100644 --- a/packages/flutter/test/material/segmented_button_test.dart +++ b/packages/flutter/test/material/segmented_button_test.dart @@ -16,18 +16,25 @@ import 'package:flutter_test/flutter_test.dart'; import '../widgets/semantics_tester.dart'; -Widget boilerplate({required Widget child}) { - return Directionality( - textDirection: TextDirection.ltr, - child: Center(child: child), - ); -} - void main() { RenderObject getOverlayColor(WidgetTester tester) { return tester.allRenderObjects.firstWhere((RenderObject object) => object.runtimeType.toString() == '_RenderInkFeatures'); } + Widget boilerplate({required Widget child}) { + return Directionality( + textDirection: TextDirection.ltr, + child: Center(child: child), + ); + } + + TextStyle iconStyle(WidgetTester tester, IconData icon) { + final RichText iconRichText = tester.widget( + find.descendant(of: find.byIcon(icon), matching: find.byType(RichText)), + ); + return iconRichText.text.style!; + } + testWidgets('SegmentsButton when compositing does not crash', (WidgetTester tester) async { // Regression test for https://github.com/flutter/flutter/issues/135747 // If the render object holds on to a stale canvas reference, this will @@ -1204,6 +1211,52 @@ void main() { matchesGoldenFile('segmented_button_test_vertical.png'), ); }); + + // Regression test for https://github.com/flutter/flutter/issues/154798. + testWidgets('SegmentedButton.styleFrom can customize the button icon', (WidgetTester tester) async { + const Color iconColor = Color(0xFFF000FF); + const double iconSize = 32.0; + const Color disabledIconColor = Color(0xFFFFF000); + Widget buildButton({ bool enabled = true }) { + return MaterialApp( + home: Material( + child: Center( + child: SegmentedButton( + style: SegmentedButton.styleFrom( + iconColor: iconColor, + iconSize: iconSize, + disabledIconColor: disabledIconColor, + ), + segments: const >[ + ButtonSegment( + value: 0, + label: Text('Add'), + icon: Icon(Icons.add), + ), + ButtonSegment( + value: 1, + label: Text('Subtract'), + icon: Icon(Icons.remove), + ), + ], + showSelectedIcon: false, + onSelectionChanged: enabled ? (Set selected) {} : null, + selected: const {0}, + ), + ), + ), + ); + } + + // Test enabled button. + await tester.pumpWidget(buildButton()); + expect(tester.getSize(find.byIcon(Icons.add)), const Size(iconSize, iconSize)); + expect(iconStyle(tester, Icons.add).color, iconColor); + + // Test disabled button. + await tester.pumpWidget(buildButton(enabled: false)); + expect(iconStyle(tester, Icons.add).color, disabledIconColor); + }); } Set enabled = const {}; diff --git a/packages/flutter/test/material/text_button_test.dart b/packages/flutter/test/material/text_button_test.dart index 0452c28062..158be30081 100644 --- a/packages/flutter/test/material/text_button_test.dart +++ b/packages/flutter/test/material/text_button_test.dart @@ -10,6 +10,13 @@ import 'package:flutter_test/flutter_test.dart'; import '../widgets/semantics_tester.dart'; void main() { + TextStyle iconStyle(WidgetTester tester, IconData icon) { + final RichText iconRichText = tester.widget( + find.descendant(of: find.byIcon(icon), matching: find.byType(RichText)), + ); + return iconRichText.text.style!; + } + testWidgets('TextButton, TextButton.icon defaults', (WidgetTester tester) async { const ColorScheme colorScheme = ColorScheme.light(); final ThemeData theme = ThemeData.from(colorScheme: colorScheme); @@ -584,14 +591,13 @@ void main() { ), ); - Color? iconColor() => _iconStyle(tester, Icons.add)?.color; // Default, not disabled. - expect(iconColor(), equals(defaultColor)); + expect(iconStyle(tester, Icons.add).color, equals(defaultColor)); // Focused. focusNode.requestFocus(); await tester.pumpAndSettle(); - expect(iconColor(), focusedColor); + expect(iconStyle(tester, Icons.add).color, focusedColor); // Hovered. final Offset center = tester.getCenter(find.byKey(buttonKey)); @@ -601,13 +607,13 @@ void main() { await gesture.addPointer(); await gesture.moveTo(center); await tester.pumpAndSettle(); - expect(iconColor(), hoverColor); + expect(iconStyle(tester, Icons.add).color, hoverColor); // Highlighted (pressed). await gesture.down(center); await tester.pump(); // Start the splash and highlight animations. await tester.pump(const Duration(milliseconds: 800)); // Wait for splash and highlight to be well under way. - expect(iconColor(), pressedColor); + expect(iconStyle(tester, Icons.add).color, pressedColor); focusNode.dispose(); }); @@ -2028,8 +2034,7 @@ void main() { Material material = tester.widget(buttonMaterial); expect(material.textStyle!.color, colorScheme.primary); - Color? iconColor() => _iconStyle(tester, Icons.add)?.color; - expect(iconColor(), equals(Colors.red)); + expect(iconStyle(tester, Icons.add).color, equals(Colors.red)); // disabled button await tester.pumpWidget( @@ -2054,7 +2059,7 @@ void main() { material = tester.widget(buttonMaterial); expect(material.textStyle!.color, colorScheme.onSurface.withOpacity(0.38)); - expect(iconColor(), equals(Colors.blue)); + expect(iconStyle(tester, Icons.add).color, equals(Colors.blue)); }); testWidgets("TextButton.styleFrom doesn't throw exception on passing only one cursor", (WidgetTester tester) async { @@ -2457,11 +2462,38 @@ void main() { expect(overlayColor(), paints..rect(color: theme.colorScheme.primary.withOpacity(0.08))); expect(hasBeenHovered, isTrue); }); -} -TextStyle? _iconStyle(WidgetTester tester, IconData icon) { - final RichText iconRichText = tester.widget( - find.descendant(of: find.byIcon(icon), matching: find.byType(RichText)), - ); - return iconRichText.text.style; + // Regression test for https://github.com/flutter/flutter/issues/154798. + testWidgets('TextButton.styleFrom can customize the button icon', (WidgetTester tester) async { + const Color iconColor = Color(0xFFF000FF); + const double iconSize = 32.0; + const Color disabledIconColor = Color(0xFFFFF000); + Widget buildButton({ bool enabled = true }) { + return MaterialApp( + home: Material( + child: Center( + child: TextButton.icon( + style: TextButton.styleFrom( + iconColor: iconColor, + iconSize: iconSize, + disabledIconColor: disabledIconColor, + ), + onPressed: enabled ? () {} : null, + icon: const Icon(Icons.add), + label: const Text('Button'), + ), + ), + ), + ); + } + + // Test enabled button. + await tester.pumpWidget(buildButton()); + expect(tester.getSize(find.byIcon(Icons.add)), const Size(iconSize, iconSize)); + expect(iconStyle(tester, Icons.add).color, iconColor); + + // Test disabled button. + await tester.pumpWidget(buildButton(enabled: false)); + expect(iconStyle(tester, Icons.add).color, disabledIconColor); + }); }