From 60d0bfcca469e59d753103f3933de411e56f7614 Mon Sep 17 00:00:00 2001 From: Taha Tesser Date: Thu, 13 Feb 2025 10:33:12 +0200 Subject: [PATCH] Fix buttons with icons ignore provided `foregroundColor` (#162880) Fixes [Flutter 3.27 and later breaks past styling and theming of icon color on buttons with icons](https://github.com/flutter/flutter/issues/162839) ### Description This PR fixes how the icon color is resolved in `ButtonStyleButton`. This was regressed in https://github.com/flutter/flutter/pull/143501. ### Code Sample (taken from issue)
expand to view the code sample ```dart import 'package:flutter/material.dart'; void main() => runApp(const MyApp()); final ButtonStyle filledButtonStyle = FilledButton.styleFrom( foregroundColor: Colors.red, backgroundColor: Colors.grey, ); final ButtonStyle elevatedButtonStyle = ElevatedButton.styleFrom( foregroundColor: Colors.orange.shade600, backgroundColor: Colors.blueGrey, ); final ButtonStyle outlinedButtonStyle = OutlinedButton.styleFrom( foregroundColor: Colors.lightBlue, ); final ButtonStyle textButtonStyle = TextButton.styleFrom( foregroundColor: Colors.green, ); final ButtonStyle segmentedButtonStyle = SegmentedButton.styleFrom( selectedForegroundColor: Colors.tealAccent.shade700, ); class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( debugShowCheckedModeBanner: false, home: const HomePage(), theme: ThemeData( filledButtonTheme: FilledButtonThemeData( style: filledButtonStyle, ), elevatedButtonTheme: ElevatedButtonThemeData( style: elevatedButtonStyle, ), outlinedButtonTheme: OutlinedButtonThemeData( style: outlinedButtonStyle, ), textButtonTheme: TextButtonThemeData( style: textButtonStyle, ), segmentedButtonTheme: SegmentedButtonThemeData( style: segmentedButtonStyle, ), ), ); } } class HomePage extends StatelessWidget { const HomePage({super.key}); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Button Icon Color Issue')), body: Center( child: Column( children: [ Row( mainAxisAlignment: MainAxisAlignment.center, children: [ FilledButton.icon( label: const Text('Filled Themed'), icon: const Icon(Icons.add), onPressed: () {}, ), const SizedBox(width: 8), FilledButton.icon( style: filledButtonStyle.copyWith( foregroundColor: WidgetStateProperty.all(Colors.yellow), ), label: const Text('Filled Styled'), icon: const Icon(Icons.add), onPressed: () {}, ), ], ), const SizedBox(height: 8), Row( mainAxisAlignment: MainAxisAlignment.center, children: [ ElevatedButton.icon( label: const Text('Elevated Themed'), icon: const Icon(Icons.add), onPressed: () {}, ), const SizedBox(width: 8), ElevatedButton.icon( style: elevatedButtonStyle.copyWith( foregroundColor: WidgetStateProperty.all(Colors.lime), ), label: const Text('Elevated Styled'), icon: const Icon(Icons.add), onPressed: () {}, ), ], ), const SizedBox(height: 8), Row( mainAxisAlignment: MainAxisAlignment.center, children: [ OutlinedButton.icon( label: const Text('Outlined Themed'), icon: const Icon(Icons.add), onPressed: () {}, ), const SizedBox(width: 8), OutlinedButton.icon( style: outlinedButtonStyle.copyWith( foregroundColor: WidgetStateProperty.all(Colors.deepOrange), ), label: const Text('Outlined Styled'), icon: const Icon(Icons.add), onPressed: () {}, ), ], ), const SizedBox(height: 8), Row( mainAxisAlignment: MainAxisAlignment.center, children: [ TextButton.icon( label: const Text('Text Themed'), icon: const Icon(Icons.add), onPressed: () {}, ), const SizedBox(width: 8), TextButton.icon( style: textButtonStyle.copyWith( foregroundColor: WidgetStateProperty.all(Colors.pink), ), label: const Text('Text Styled'), icon: const Icon(Icons.add), onPressed: () {}, ), ], ), const SizedBox(height: 8), const SegmentedButtonShowcase(), ], ), ), ); } } class SegmentedButtonShowcase extends StatefulWidget { const SegmentedButtonShowcase({this.showOutlinedButton, super.key}); final bool? showOutlinedButton; @override State createState() => _SegmentedButtonShowcaseState(); } enum Calendar { day, week, month, year } class _SegmentedButtonShowcaseState extends State { Calendar _selected = Calendar.day; @override Widget build(BuildContext context) { return SegmentedButton( segments: const >[ ButtonSegment( value: Calendar.day, label: Text('Day'), icon: Icon(Icons.calendar_view_day), ), ButtonSegment( value: Calendar.week, icon: Icon(Icons.calendar_view_week), label: Text('Week'), ), ButtonSegment( value: Calendar.month, icon: Icon(Icons.calendar_view_month), label: Text('Mont'), ), ButtonSegment( value: Calendar.year, icon: Icon(Icons.calendar_today), label: Text('Year'), ), ], selected: {_selected}, onSelectionChanged: (Set selected) { setState(() { _selected = selected.first; }); }, ); } } ```
### Before Screenshot 2025-02-07 at 17 45 46 ### After Screenshot 2025-02-07 at 17 45 37 ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [ ] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md --- .../lib/src/material/button_style_button.dart | 17 +++++-- .../lib/src/material/elevated_button.dart | 3 ++ .../lib/src/material/filled_button.dart | 3 ++ .../lib/src/material/outlined_button.dart | 3 ++ .../flutter/lib/src/material/text_button.dart | 3 ++ .../test/material/elevated_button_test.dart | 23 ++++++++++ .../material/elevated_button_theme_test.dart | 40 +++++++++++++++- .../test/material/filled_button_test.dart | 34 ++++++++++++++ .../material/filled_button_theme_test.dart | 46 +++++++++++++++++++ .../test/material/outlined_button_test.dart | 23 ++++++++++ .../material/outlined_button_theme_test.dart | 36 +++++++++++++++ .../test/material/text_button_test.dart | 23 ++++++++++ .../test/material/text_button_theme_test.dart | 36 +++++++++++++++ 13 files changed, 283 insertions(+), 7 deletions(-) diff --git a/packages/flutter/lib/src/material/button_style_button.dart b/packages/flutter/lib/src/material/button_style_button.dart index 1885214bfe..23c2ac57ff 100644 --- a/packages/flutter/lib/src/material/button_style_button.dart +++ b/packages/flutter/lib/src/material/button_style_button.dart @@ -389,6 +389,16 @@ class _ButtonStyleState extends State with TickerProviderStat }); } + Color? effectiveIconColor() { + return widgetStyle?.iconColor?.resolve(statesController.value) ?? + themeStyle?.iconColor?.resolve(statesController.value) ?? + widgetStyle?.foregroundColor?.resolve(statesController.value) ?? + themeStyle?.foregroundColor?.resolve(statesController.value) ?? + defaultStyle.iconColor?.resolve(statesController.value) ?? + // Fallback to foregroundColor if iconColor is null. + defaultStyle.foregroundColor?.resolve(statesController.value); + } + final double? resolvedElevation = resolve((ButtonStyle? style) => style?.elevation); final TextStyle? resolvedTextStyle = resolve( (ButtonStyle? style) => style?.textStyle, @@ -409,7 +419,7 @@ class _ButtonStyleState extends State with TickerProviderStat final Size? resolvedMinimumSize = resolve((ButtonStyle? style) => style?.minimumSize); final Size? resolvedFixedSize = resolve((ButtonStyle? style) => style?.fixedSize); final Size? resolvedMaximumSize = resolve((ButtonStyle? style) => style?.maximumSize); - final Color? resolvedIconColor = resolve((ButtonStyle? style) => style?.iconColor); + final Color? resolvedIconColor = effectiveIconColor(); final double? resolvedIconSize = resolve((ButtonStyle? style) => style?.iconSize); final BorderSide? resolvedSide = resolve((ButtonStyle? style) => style?.side); final OutlinedBorder? resolvedShape = resolve( @@ -551,10 +561,7 @@ class _ButtonStyleState extends State with TickerProviderStat customBorder: resolvedShape!.copyWith(side: resolvedSide), statesController: statesController, child: IconTheme.merge( - data: IconThemeData( - color: resolvedIconColor ?? resolvedForegroundColor, - size: resolvedIconSize, - ), + data: IconThemeData(color: resolvedIconColor, size: resolvedIconSize), child: result, ), ); diff --git a/packages/flutter/lib/src/material/elevated_button.dart b/packages/flutter/lib/src/material/elevated_button.dart index d8aef58e14..73b572c11c 100644 --- a/packages/flutter/lib/src/material/elevated_button.dart +++ b/packages/flutter/lib/src/material/elevated_button.dart @@ -161,6 +161,9 @@ class ElevatedButton extends ButtonStyleButton { /// [ButtonStyle.iconColor] and [iconSize] is used to construct /// [ButtonStyle.iconSize]. /// + /// If [iconColor] is null, the button icon will use [foregroundColor]. If [foregroundColor] is also + /// null, the button icon will use the default icon color. + /// /// 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 diff --git a/packages/flutter/lib/src/material/filled_button.dart b/packages/flutter/lib/src/material/filled_button.dart index be27cf852c..6c776c551b 100644 --- a/packages/flutter/lib/src/material/filled_button.dart +++ b/packages/flutter/lib/src/material/filled_button.dart @@ -232,6 +232,9 @@ class FilledButton extends ButtonStyleButton { /// [ButtonStyle.iconColor] and [iconSize] is used to construct /// [ButtonStyle.iconSize]. /// + /// If [iconColor] is null, the button icon will use [foregroundColor]. If [foregroundColor] is also + /// null, the button icon will use the default icon color. + /// /// 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 diff --git a/packages/flutter/lib/src/material/outlined_button.dart b/packages/flutter/lib/src/material/outlined_button.dart index 2647394201..82191c60b7 100644 --- a/packages/flutter/lib/src/material/outlined_button.dart +++ b/packages/flutter/lib/src/material/outlined_button.dart @@ -160,6 +160,9 @@ class OutlinedButton extends ButtonStyleButton { /// [ButtonStyle.iconColor] and [iconSize] is used to construct /// [ButtonStyle.iconSize]. /// + /// If [iconColor] is null, the button icon will use [foregroundColor]. If [foregroundColor] is also + /// null, the button icon will use the default icon color. + /// /// If [overlayColor] is specified and its value is [Colors.transparent] /// then the pressed/focused/hovered highlights are effectively defeated. /// Otherwise a [WidgetStateProperty] with the same opacities as the diff --git a/packages/flutter/lib/src/material/text_button.dart b/packages/flutter/lib/src/material/text_button.dart index f863f91726..d665ec7170 100644 --- a/packages/flutter/lib/src/material/text_button.dart +++ b/packages/flutter/lib/src/material/text_button.dart @@ -169,6 +169,9 @@ class TextButton extends ButtonStyleButton { /// [ButtonStyle.iconColor] and [iconSize] is used to construct /// [ButtonStyle.iconSize]. /// + /// If [iconColor] is null, the button icon will use [foregroundColor]. If [foregroundColor] is also + /// null, the button icon will use the default icon color. + /// /// If [overlayColor] is specified and its value is [Colors.transparent] /// then the pressed/focused/hovered highlights are effectively defeated. /// Otherwise a [WidgetStateProperty] with the same opacities as the diff --git a/packages/flutter/test/material/elevated_button_test.dart b/packages/flutter/test/material/elevated_button_test.dart index 018ce9ecc1..282ec93863 100644 --- a/packages/flutter/test/material/elevated_button_test.dart +++ b/packages/flutter/test/material/elevated_button_test.dart @@ -2510,4 +2510,27 @@ void main() { final Offset iconTopRight = tester.getTopRight(find.byIcon(Icons.add)); expect(buttonTopRight.dx, iconTopRight.dx + 24.0); }); + + // Regression test for https://github.com/flutter/flutter/issues/162839. + testWidgets('ElevatedButton icon uses provided foregroundColor over default icon color', ( + WidgetTester tester, + ) async { + const Color foregroundColor = Color(0xFFFF1234); + + await tester.pumpWidget( + MaterialApp( + home: Material( + child: Center( + child: ElevatedButton.icon( + style: ElevatedButton.styleFrom(foregroundColor: foregroundColor), + onPressed: () {}, + icon: const Icon(Icons.add), + label: const Text('Button'), + ), + ), + ), + ), + ); + expect(iconStyle(tester, Icons.add).color, foregroundColor); + }); } diff --git a/packages/flutter/test/material/elevated_button_theme_test.dart b/packages/flutter/test/material/elevated_button_theme_test.dart index f618f70673..536aa0bee8 100644 --- a/packages/flutter/test/material/elevated_button_theme_test.dart +++ b/packages/flutter/test/material/elevated_button_theme_test.dart @@ -6,6 +6,13 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.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!; + } + test('ElevatedButtonThemeData lerp special cases', () { expect(ElevatedButtonThemeData.lerp(null, null, 0), null); const ElevatedButtonThemeData data = ElevatedButtonThemeData(); @@ -263,7 +270,7 @@ void main() { ); }); - testWidgets('Material3 - ElevatedButton repsects Theme shadowColor', (WidgetTester tester) async { + testWidgets('Material3 - ElevatedButton respects Theme shadowColor', (WidgetTester tester) async { const ColorScheme colorScheme = ColorScheme.light(); const Color shadowColor = Color(0xff000001); const Color overriddenColor = Color(0xff000002); @@ -334,7 +341,7 @@ void main() { expect(material.shadowColor, shadowColor); }); - testWidgets('Material2 - ElevatedButton repsects Theme shadowColor', (WidgetTester tester) async { + testWidgets('Material2 - ElevatedButton respects Theme shadowColor', (WidgetTester tester) async { const ColorScheme colorScheme = ColorScheme.light(); const Color shadowColor = Color(0xff000001); const Color overriddenColor = Color(0xff000002); @@ -442,4 +449,33 @@ void main() { expect(buttonTopRight.dx, iconTopRight.dx + 24.0); }); + + // Regression test for https://github.com/flutter/flutter/issues/162839. + testWidgets( + 'ElevatedButton icon uses provided ElevatedButtonTheme foregroundColor over default icon color', + (WidgetTester tester) async { + const Color foregroundColor = Color(0xFFFFA500); + + await tester.pumpWidget( + MaterialApp( + theme: ThemeData( + elevatedButtonTheme: ElevatedButtonThemeData( + style: ElevatedButton.styleFrom(foregroundColor: foregroundColor), + ), + ), + home: Material( + child: Center( + child: ElevatedButton.icon( + onPressed: () {}, + icon: const Icon(Icons.add), + label: const Text('Button'), + ), + ), + ), + ), + ); + + expect(iconStyle(tester, Icons.add).color, foregroundColor); + }, + ); } diff --git a/packages/flutter/test/material/filled_button_test.dart b/packages/flutter/test/material/filled_button_test.dart index 393c182ec1..8e36e80e09 100644 --- a/packages/flutter/test/material/filled_button_test.dart +++ b/packages/flutter/test/material/filled_button_test.dart @@ -2756,4 +2756,38 @@ void main() { final Offset iconTopRight = tester.getTopRight(find.byIcon(Icons.add)); expect(buttonTopRight.dx, iconTopRight.dx + 24.0); }); + + // Regression test for https://github.com/flutter/flutter/issues/162839. + testWidgets('FilledButton icon uses provided foregroundColor over default icon color', ( + WidgetTester tester, + ) async { + const Color foregroundColor = Color(0xFFFF1234); + + await tester.pumpWidget( + MaterialApp( + home: Material( + child: Center( + child: Column( + children: [ + FilledButton.icon( + style: FilledButton.styleFrom(foregroundColor: foregroundColor), + onPressed: () {}, + icon: const Icon(Icons.add), + label: const Text('Button'), + ), + FilledButton.tonalIcon( + style: FilledButton.styleFrom(foregroundColor: foregroundColor), + onPressed: () {}, + icon: const Icon(Icons.mail), + label: const Text('Button'), + ), + ], + ), + ), + ), + ), + ); + expect(iconStyle(tester, Icons.add).color, foregroundColor); + expect(iconStyle(tester, Icons.mail).color, foregroundColor); + }); } diff --git a/packages/flutter/test/material/filled_button_theme_test.dart b/packages/flutter/test/material/filled_button_theme_test.dart index a661cb9c2c..a606fffbc5 100644 --- a/packages/flutter/test/material/filled_button_theme_test.dart +++ b/packages/flutter/test/material/filled_button_theme_test.dart @@ -6,6 +6,13 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.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!; + } + test('FilledButtonThemeData lerp special cases', () { expect(FilledButtonThemeData.lerp(null, null, 0), null); const FilledButtonThemeData data = FilledButtonThemeData(); @@ -360,4 +367,43 @@ void main() { expect(buttonTopRight.dx, iconTopRight.dx + 24.0); }); + + // Regression test for https://github.com/flutter/flutter/issues/162839. + testWidgets( + 'FilledButton icon uses provided FilledButtonTheme foregroundColor over default icon color', + (WidgetTester tester) async { + const Color foregroundColor = Color(0xFFFFA500); + + await tester.pumpWidget( + MaterialApp( + theme: ThemeData( + filledButtonTheme: FilledButtonThemeData( + style: FilledButton.styleFrom(foregroundColor: foregroundColor), + ), + ), + home: Material( + child: Center( + child: Column( + children: [ + FilledButton.icon( + onPressed: () {}, + icon: const Icon(Icons.add), + label: const Text('Button'), + ), + FilledButton.icon( + onPressed: () {}, + icon: const Icon(Icons.mail), + label: const Text('Button'), + ), + ], + ), + ), + ), + ), + ); + + expect(iconStyle(tester, Icons.add).color, foregroundColor); + expect(iconStyle(tester, Icons.mail).color, foregroundColor); + }, + ); } diff --git a/packages/flutter/test/material/outlined_button_test.dart b/packages/flutter/test/material/outlined_button_test.dart index 4cb7abbaa0..2ec683286b 100644 --- a/packages/flutter/test/material/outlined_button_test.dart +++ b/packages/flutter/test/material/outlined_button_test.dart @@ -2854,4 +2854,27 @@ void main() { final Offset iconTopRight = tester.getTopRight(find.byIcon(Icons.add)); expect(buttonTopRight.dx, iconTopRight.dx + 24.0); }); + + // Regression test for https://github.com/flutter/flutter/issues/162839. + testWidgets('OutlinedButton icon uses provided foregroundColor over default icon color', ( + WidgetTester tester, + ) async { + const Color foregroundColor = Color(0xFFFF1234); + + await tester.pumpWidget( + MaterialApp( + home: Material( + child: Center( + child: OutlinedButton.icon( + style: OutlinedButton.styleFrom(foregroundColor: foregroundColor), + onPressed: () {}, + icon: const Icon(Icons.add), + label: const Text('Button'), + ), + ), + ), + ), + ); + expect(iconStyle(tester, Icons.add).color, foregroundColor); + }); } diff --git a/packages/flutter/test/material/outlined_button_theme_test.dart b/packages/flutter/test/material/outlined_button_theme_test.dart index e56b01aaed..45242e6e39 100644 --- a/packages/flutter/test/material/outlined_button_theme_test.dart +++ b/packages/flutter/test/material/outlined_button_theme_test.dart @@ -6,6 +6,13 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.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!; + } + test('OutlinedButtonThemeData lerp special cases', () { expect(OutlinedButtonThemeData.lerp(null, null, 0), null); const OutlinedButtonThemeData data = OutlinedButtonThemeData(); @@ -446,4 +453,33 @@ void main() { expect(buttonTopRight.dx, iconTopRight.dx + 24.0); }, ); + + // Regression test for https://github.com/flutter/flutter/issues/162839. + testWidgets( + 'OutlinedButton icon uses provided OutlinedButtonTheme foregroundColor over default icon color', + (WidgetTester tester) async { + const Color foregroundColor = Color(0xFFFFA500); + + await tester.pumpWidget( + MaterialApp( + theme: ThemeData( + outlinedButtonTheme: OutlinedButtonThemeData( + style: OutlinedButton.styleFrom(foregroundColor: foregroundColor), + ), + ), + home: Material( + child: Center( + child: OutlinedButton.icon( + onPressed: () {}, + icon: const Icon(Icons.add), + label: const Text('Button'), + ), + ), + ), + ), + ); + + expect(iconStyle(tester, Icons.add).color, foregroundColor); + }, + ); } diff --git a/packages/flutter/test/material/text_button_test.dart b/packages/flutter/test/material/text_button_test.dart index 683c958b68..c241fb91cf 100644 --- a/packages/flutter/test/material/text_button_test.dart +++ b/packages/flutter/test/material/text_button_test.dart @@ -2547,4 +2547,27 @@ void main() { final Offset iconTopRight = tester.getTopRight(find.byIcon(Icons.add)); expect(buttonTopRight.dx, iconTopRight.dx + 16.0); }); + + // Regression test for https://github.com/flutter/flutter/issues/162839. + testWidgets('TextButton icon uses provided foregroundColor over default icon color', ( + WidgetTester tester, + ) async { + const Color foregroundColor = Color(0xFFFF1234); + + await tester.pumpWidget( + MaterialApp( + home: Material( + child: Center( + child: TextButton.icon( + style: TextButton.styleFrom(foregroundColor: foregroundColor), + onPressed: () {}, + icon: const Icon(Icons.add), + label: const Text('Button'), + ), + ), + ), + ), + ); + expect(iconStyle(tester, Icons.add).color, foregroundColor); + }); } diff --git a/packages/flutter/test/material/text_button_theme_test.dart b/packages/flutter/test/material/text_button_theme_test.dart index 04aa4e5c36..171a45b36c 100644 --- a/packages/flutter/test/material/text_button_theme_test.dart +++ b/packages/flutter/test/material/text_button_theme_test.dart @@ -6,6 +6,13 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.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!; + } + test('TextButtonTheme lerp special cases', () { expect(TextButtonThemeData.lerp(null, null, 0), null); const TextButtonThemeData data = TextButtonThemeData(); @@ -447,4 +454,33 @@ void main() { expect(buttonTopRight.dx, iconTopRight.dx + 16.0); }); + + // Regression test for https://github.com/flutter/flutter/issues/162839. + testWidgets( + 'TextButton icon uses provided TextButtonThemeData foregroundColor over default icon color', + (WidgetTester tester) async { + const Color foregroundColor = Color(0xFFFFA500); + + await tester.pumpWidget( + MaterialApp( + theme: ThemeData( + textButtonTheme: TextButtonThemeData( + style: TextButton.styleFrom(foregroundColor: foregroundColor), + ), + ), + home: Material( + child: Center( + child: TextButton.icon( + onPressed: () {}, + icon: const Icon(Icons.add), + label: const Text('Button'), + ), + ), + ), + ), + ); + + expect(iconStyle(tester, Icons.add).color, foregroundColor); + }, + ); }