Fix missing icon props in button styleFrom methods (#154821)

Fixes [Add missing icon props in button `styleFrom`  methods.](https://github.com/flutter/flutter/issues/154798)

### Description

Add missing icon propers in the following widgets:

- `ElevatedButton.styleFrom` (missing `iconSize`)
- `FilledButton.styleFrom` (missing `iconSize`)
- `OutlinedButton.styleFrom` (missing `iconSize`)
- `TextButton.styleFrom` (missing `iconSize`)
- `MenuItemButton.styleFrom` (missing `iconSize` and `disabledIconColor`)
- `SubmenuButton.styleFrom` (missing `iconSize` and `disabledIconColor`)
- `SegmentedButton.styleFrom` (missing `iconSize`, `iconColor`, and `disabledIconColor`)

### Code sample

<details>
<summary>expand to view the code sample</summary> 

```dart
import 'package:flutter/material.dart';

enum Calendar { day, week, month, year }

void main() => runApp(const MyApp());

class MyApp extends StatefulWidget {
  const MyApp({super.key});

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  Calendar calendarView = Calendar.week;
  bool isEnabled = true;

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: Scaffold(
        body: Center(
          child: Column(
            spacing: 10.0,
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              ElevatedButton.icon(
                style: ElevatedButton.styleFrom(
                  iconSize: 30,
                  iconColor: Colors.red,
                  disabledIconColor: Colors.red.withValues(alpha: 0.5),
                ),
                onPressed: isEnabled ? () {} : null,
                icon: const Icon(Icons.add),
                label: const Text('ElevatedButton'),
              ),
              FilledButton.icon(
                style: ElevatedButton.styleFrom(
                  iconSize: 30,
                  iconColor: Colors.red,
                  disabledIconColor: Colors.red.withValues(alpha: 0.5),
                ),
                onPressed: isEnabled ? () {} : null,
                icon: const Icon(Icons.add),
                label: const Text('FilledButton'),
              ),
              FilledButton.tonalIcon(
                style: ElevatedButton.styleFrom(
                  iconSize: 30,
                  iconColor: Colors.red,
                  disabledIconColor: Colors.red.withValues(alpha: 0.5),
                ),
                onPressed: isEnabled ? () {} : null,
                icon: const Icon(Icons.add),
                label: const Text('Add'),
              ),
              OutlinedButton.icon(
                style: ElevatedButton.styleFrom(
                  iconSize: 30,
                  iconColor: Colors.red,
                  disabledIconColor: Colors.red.withValues(alpha: 0.5),
                ),
                onPressed: isEnabled ? () {} : null,
                icon: const Icon(Icons.add),
                label: const Text('OutlinedButton'),
              ),
              TextButton.icon(
                style: ElevatedButton.styleFrom(
                  iconSize: 30,
                  iconColor: Colors.red,
                  disabledIconColor: Colors.red.withValues(alpha: 0.5),
                ),
                onPressed: isEnabled ? () {} : null,
                icon: const Icon(Icons.add),
                label: const Text('TextButton'),
              ),
              SizedBox(
                width: 200,
                child: MenuItemButton(
                  style: MenuItemButton.styleFrom(
                    iconSize: 30,
                    iconColor: Colors.red,
                    disabledIconColor: Colors.red.withValues(alpha: 0.5),
                  ),
                  trailingIcon: const Icon(Icons.arrow_forward_ios),
                  onPressed: isEnabled ? () {} : null,
                  child: const Text('MenuItemButton'),
                ),
              ),
              SizedBox(
                width: 200,
                child: SubmenuButton(
                  style: SubmenuButton.styleFrom(
                    iconSize: 30,
                    iconColor: Colors.red,
                    disabledIconColor: Colors.red.withValues(alpha: 0.5),
                  ),
                  trailingIcon: const Icon(Icons.arrow_forward_ios),
                  menuChildren: <Widget>[
                    if (isEnabled) const Text('Item'),
                  ],
                  child: const Text('SubmenuButton'),
                ),
              ),
              SegmentedButton<Calendar>(
                style: SegmentedButton.styleFrom(
                  iconColor: Colors.red,
                  iconSize: 30,
                  disabledIconColor: Colors.red.withValues(alpha: 0.5),
                ),
                segments: const <ButtonSegment<Calendar>>[
                  ButtonSegment<Calendar>(
                      value: Calendar.day,
                      label: Text('Day'),
                      icon: Icon(Icons.calendar_view_day)),
                  ButtonSegment<Calendar>(
                      value: Calendar.week,
                      label: Text('Week'),
                      icon: Icon(Icons.calendar_view_week)),
                  ButtonSegment<Calendar>(
                      value: Calendar.month,
                      label: Text('Month'),
                      icon: Icon(Icons.calendar_view_month)),
                  ButtonSegment<Calendar>(
                      value: Calendar.year,
                      label: Text('Year'),
                      icon: Icon(Icons.calendar_today)),
                ],
                selected: <Calendar>{calendarView},
                onSelectionChanged:
                    isEnabled ? (Set<Calendar> newSelection) {} : null,
              )
            ],
          ),
        ),
        floatingActionButton: FloatingActionButton.extended(
          onPressed: () {
            setState(() {
              isEnabled = !isEnabled;
            });
          },
          label: Text(isEnabled ? 'Enabled' : 'Disabled'),
        ),
      ),
    );
  }
}
```

</details>

### Preview (Customized using icon props in `styleFrom`  methods)

<img width="838" alt="Screenshot 2024-09-09 at 16 27 19" src="https://github.com/user-attachments/assets/551d328b-307f-4f63-b0e8-1358a12877f9">
This commit is contained in:
Taha Tesser 2024-09-18 03:19:40 +03:00 committed by GitHub
parent d0a9e3be94
commit d5e843eca3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 376 additions and 66 deletions

View File

@ -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<Color>(shadowColor),
surfaceTintColor: ButtonStyleButton.allOrNull<Color>(surfaceTintColor),
iconColor: ButtonStyleButton.defaultColor(iconColor, disabledIconColor),
iconSize: ButtonStyleButton.allOrNull<double>(iconSize),
elevation: elevationValue,
padding: ButtonStyleButton.allOrNull<EdgeInsetsGeometry>(padding),
minimumSize: ButtonStyleButton.allOrNull<Size>(minimumSize),

View File

@ -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<Color>(shadowColor),
surfaceTintColor: ButtonStyleButton.allOrNull<Color>(surfaceTintColor),
iconColor: ButtonStyleButton.defaultColor(iconColor, disabledIconColor),
iconSize: ButtonStyleButton.allOrNull<double>(iconSize),
elevation: ButtonStyleButton.allOrNull(elevation),
padding: ButtonStyleButton.allOrNull<EdgeInsetsGeometry>(padding),
minimumSize: ButtonStyleButton.allOrNull<Size>(minimumSize),

View File

@ -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,

View File

@ -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<Color>(shadowColor),
surfaceTintColor: ButtonStyleButton.allOrNull<Color>(surfaceTintColor),
iconColor: ButtonStyleButton.defaultColor(iconColor, disabledIconColor),
iconSize: ButtonStyleButton.allOrNull<double>(iconSize),
elevation: ButtonStyleButton.allOrNull<double>(elevation),
padding: ButtonStyleButton.allOrNull<EdgeInsetsGeometry>(padding),
minimumSize: ButtonStyleButton.allOrNull<Size>(minimumSize),

View File

@ -232,6 +232,10 @@ class SegmentedButton<T> 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<T> 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<T> extends StatefulWidget {
textStyle: textStyle,
shadowColor: shadowColor,
surfaceTintColor: surfaceTintColor,
iconColor: iconColor,
iconSize: iconSize,
disabledIconColor: disabledIconColor,
elevation: elevation,
padding: padding,
minimumSize: minimumSize,

View File

@ -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<Color>(shadowColor),
surfaceTintColor: ButtonStyleButton.allOrNull<Color>(surfaceTintColor),
iconColor: iconColorProp,
iconSize: ButtonStyleButton.allOrNull<double>(iconSize),
elevation: ButtonStyleButton.allOrNull<double>(elevation),
padding: ButtonStyleButton.allOrNull<EdgeInsetsGeometry>(padding),
minimumSize: ButtonStyleButton.allOrNull<Size>(minimumSize),

View File

@ -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<RichText>(
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<RichText>(
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);
});
}

View File

@ -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<RichText>(
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<RichText>(
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);
});
}

View File

@ -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<RichText>(
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: <Widget>[
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<Widget> createTestMenus({

View File

@ -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<RichText>(
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<RichText>(
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);
});
}

View File

@ -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<RichText>(
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<int>(
style: SegmentedButton.styleFrom(
iconColor: iconColor,
iconSize: iconSize,
disabledIconColor: disabledIconColor,
),
segments: const <ButtonSegment<int>>[
ButtonSegment<int>(
value: 0,
label: Text('Add'),
icon: Icon(Icons.add),
),
ButtonSegment<int>(
value: 1,
label: Text('Subtract'),
icon: Icon(Icons.remove),
),
],
showSelectedIcon: false,
onSelectionChanged: enabled ? (Set<int> selected) {} : null,
selected: const <int>{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<MaterialState> enabled = const <MaterialState>{};

View File

@ -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<RichText>(
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<Material>(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<Material>(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<RichText>(
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);
});
}