
fixes [Add missing `overlayColor` property in `styleFrom` methods](https://github.com/flutter/flutter/issues/146636) ### Code sample <details> <summary>expand to view the code sample</summary> ```dart import 'package:flutter/material.dart'; enum Sizes { extraSmall, small, medium, large, extraLarge } void main() => runApp(const MyApp()); class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( debugShowCheckedModeBanner: false, home: Scaffold( body: Center( child: Column( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: <Widget>[ Text('styleFrom(overlayColor: Colors.red)', style: Theme.of(context).textTheme.titleLarge), TextButton( style: TextButton.styleFrom(overlayColor: Colors.red), onPressed: () {}, child: const Text('TextButton'), ), IconButton( style: IconButton.styleFrom( overlayColor: Colors.red, ), onPressed: () {}, icon: const Icon(Icons.add), ), MenuBar( children: [ MenuItemButton( style: MenuItemButton.styleFrom(overlayColor: Colors.red), child: const Text('MenuItemButton'), onPressed: () {}, ), SubmenuButton( style: SubmenuButton.styleFrom(overlayColor: Colors.red), menuChildren: [ MenuItemButton( child: const Text('MenuItemButton'), onPressed: () {}, ), ], child: const Text('SubmenuButton'), ), ], ), SegmentedButton<Sizes>( style: SegmentedButton.styleFrom(overlayColor: Colors.red), segments: const <ButtonSegment<Sizes>>[ ButtonSegment<Sizes>( value: Sizes.extraSmall, label: Text('XS')), ButtonSegment<Sizes>(value: Sizes.small, label: Text('S')), ButtonSegment<Sizes>(value: Sizes.medium, label: Text('M')), ButtonSegment<Sizes>( value: Sizes.large, label: Text('L'), ), ButtonSegment<Sizes>( value: Sizes.extraLarge, label: Text('XL')), ], selected: const {Sizes.medium}, onSelectionChanged: (Set<Sizes> newSelection) {}, multiSelectionEnabled: true, ), ], ), ), ), ); } } ``` </details> ### Preview 
317 lines
12 KiB
Dart
317 lines
12 KiB
Dart
// Copyright 2014 The Flutter Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
import 'package:flutter/gestures.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter/services.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
|
|
void main() {
|
|
RenderObject getOverlayColor(WidgetTester tester) {
|
|
return tester.allRenderObjects.firstWhere((RenderObject object) => object.runtimeType.toString() == '_RenderInkFeatures');
|
|
}
|
|
|
|
test('IconButtonThemeData lerp special cases', () {
|
|
expect(IconButtonThemeData.lerp(null, null, 0), null);
|
|
const IconButtonThemeData data = IconButtonThemeData();
|
|
expect(identical(IconButtonThemeData.lerp(data, data, 0.5), data), true);
|
|
});
|
|
|
|
testWidgets('Passing no IconButtonTheme returns defaults', (WidgetTester tester) async {
|
|
const ColorScheme colorScheme = ColorScheme.light();
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
theme: ThemeData.from(colorScheme: colorScheme, useMaterial3: true),
|
|
home: Scaffold(
|
|
body: Center(
|
|
child: IconButton(
|
|
onPressed: () { },
|
|
icon: const Icon(Icons.ac_unit),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
final Finder buttonMaterial = find.descendant(
|
|
of: find.byType(IconButton),
|
|
matching: find.byType(Material),
|
|
);
|
|
|
|
final Material material = tester.widget<Material>(buttonMaterial);
|
|
expect(material.animationDuration, const Duration(milliseconds: 200));
|
|
expect(material.borderRadius, null);
|
|
expect(material.color, Colors.transparent);
|
|
expect(material.elevation, 0.0);
|
|
expect(material.shadowColor, Colors.transparent);
|
|
expect(material.shape, const StadiumBorder());
|
|
expect(material.textStyle, null);
|
|
expect(material.type, MaterialType.button);
|
|
|
|
final Align align = tester.firstWidget<Align>(find.ancestor(of: find.byIcon(Icons.ac_unit), matching: find.byType(Align)));
|
|
expect(align.alignment, Alignment.center);
|
|
});
|
|
|
|
group('[Theme, IconTheme, IconButton style overrides]', () {
|
|
const Color foregroundColor = Color(0xff000001);
|
|
const Color disabledForegroundColor = Color(0xff000002);
|
|
const Color backgroundColor = Color(0xff000003);
|
|
const Color shadowColor = Color(0xff000004);
|
|
const double elevation = 3;
|
|
const EdgeInsets padding = EdgeInsets.all(3);
|
|
const Size minimumSize = Size(200, 200);
|
|
const BorderSide side = BorderSide(color: Colors.green, width: 2);
|
|
const OutlinedBorder shape = RoundedRectangleBorder(side: side, borderRadius: BorderRadius.all(Radius.circular(2)));
|
|
const MouseCursor enabledMouseCursor = SystemMouseCursors.text;
|
|
const MouseCursor disabledMouseCursor = SystemMouseCursors.grab;
|
|
const MaterialTapTargetSize tapTargetSize = MaterialTapTargetSize.shrinkWrap;
|
|
const Duration animationDuration = Duration(milliseconds: 25);
|
|
const bool enableFeedback = false;
|
|
const AlignmentGeometry alignment = Alignment.centerLeft;
|
|
|
|
final ButtonStyle style = IconButton.styleFrom(
|
|
foregroundColor: foregroundColor,
|
|
disabledForegroundColor: disabledForegroundColor,
|
|
backgroundColor: backgroundColor,
|
|
shadowColor: shadowColor,
|
|
elevation: elevation,
|
|
padding: padding,
|
|
minimumSize: minimumSize,
|
|
side: side,
|
|
shape: shape,
|
|
enabledMouseCursor: enabledMouseCursor,
|
|
disabledMouseCursor: disabledMouseCursor,
|
|
tapTargetSize: tapTargetSize,
|
|
animationDuration: animationDuration,
|
|
enableFeedback: enableFeedback,
|
|
alignment: alignment,
|
|
);
|
|
|
|
Widget buildFrame({ ButtonStyle? buttonStyle, ButtonStyle? themeStyle, ButtonStyle? overallStyle }) {
|
|
final Widget child = Builder(
|
|
builder: (BuildContext context) {
|
|
return IconButton(
|
|
style: buttonStyle,
|
|
onPressed: () { },
|
|
icon: const Icon(Icons.ac_unit),
|
|
);
|
|
},
|
|
);
|
|
return MaterialApp(
|
|
theme: ThemeData.from(colorScheme: const ColorScheme.light(), useMaterial3: true).copyWith(
|
|
iconButtonTheme: IconButtonThemeData(style: overallStyle),
|
|
),
|
|
home: Scaffold(
|
|
body: Center(
|
|
// If the IconButtonTheme widget is present, it's used
|
|
// instead of the Theme's ThemeData.iconButtonTheme.
|
|
child: themeStyle == null ? child : IconButtonTheme(
|
|
data: IconButtonThemeData(style: themeStyle),
|
|
child: child,
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
final Finder findMaterial = find.descendant(
|
|
of: find.byType(IconButton),
|
|
matching: find.byType(Material),
|
|
);
|
|
|
|
final Finder findInkWell = find.descendant(
|
|
of: find.byType(IconButton),
|
|
matching: find.byType(InkWell),
|
|
);
|
|
|
|
const Set<MaterialState> enabled = <MaterialState>{};
|
|
const Set<MaterialState> disabled = <MaterialState>{ MaterialState.disabled };
|
|
const Set<MaterialState> hovered = <MaterialState>{ MaterialState.hovered };
|
|
const Set<MaterialState> focused = <MaterialState>{ MaterialState.focused };
|
|
const Set<MaterialState> pressed = <MaterialState>{ MaterialState.pressed };
|
|
|
|
void checkButton(WidgetTester tester) {
|
|
final Material material = tester.widget<Material>(findMaterial);
|
|
final InkWell inkWell = tester.widget<InkWell>(findInkWell);
|
|
expect(material.textStyle, null);
|
|
expect(material.color, backgroundColor);
|
|
expect(material.shadowColor, shadowColor);
|
|
expect(material.elevation, elevation);
|
|
expect(MaterialStateProperty.resolveAs<MouseCursor?>(inkWell.mouseCursor, enabled), enabledMouseCursor);
|
|
expect(MaterialStateProperty.resolveAs<MouseCursor?>(inkWell.mouseCursor, disabled), disabledMouseCursor);
|
|
expect(inkWell.overlayColor!.resolve(hovered), foregroundColor.withOpacity(0.08));
|
|
expect(inkWell.overlayColor!.resolve(focused), foregroundColor.withOpacity(0.1));
|
|
expect(inkWell.overlayColor!.resolve(pressed), foregroundColor.withOpacity(0.1));
|
|
expect(inkWell.enableFeedback, enableFeedback);
|
|
expect(material.borderRadius, null);
|
|
expect(material.shape, shape);
|
|
expect(material.animationDuration, animationDuration);
|
|
expect(tester.getSize(find.byType(IconButton)), const Size(200, 200));
|
|
final Align align = tester.firstWidget<Align>(find.ancestor(of: find.byIcon(Icons.ac_unit), matching: find.byType(Align)));
|
|
expect(align.alignment, alignment);
|
|
}
|
|
|
|
testWidgets('Button style overrides defaults', (WidgetTester tester) async {
|
|
await tester.pumpWidget(buildFrame(buttonStyle: style));
|
|
await tester.pumpAndSettle(); // allow the animations to finish
|
|
checkButton(tester);
|
|
});
|
|
|
|
testWidgets('Button theme style overrides defaults', (WidgetTester tester) async {
|
|
await tester.pumpWidget(buildFrame(themeStyle: style));
|
|
await tester.pumpAndSettle();
|
|
checkButton(tester);
|
|
});
|
|
|
|
testWidgets('Overall Theme button theme style overrides defaults', (WidgetTester tester) async {
|
|
await tester.pumpWidget(buildFrame(overallStyle: style));
|
|
await tester.pumpAndSettle();
|
|
checkButton(tester);
|
|
});
|
|
|
|
// Same as the previous tests with empty ButtonStyle's instead of null.
|
|
|
|
testWidgets('Button style overrides defaults, empty theme and overall styles', (WidgetTester tester) async {
|
|
await tester.pumpWidget(buildFrame(buttonStyle: style, themeStyle: const ButtonStyle(), overallStyle: const ButtonStyle()));
|
|
await tester.pumpAndSettle(); // allow the animations to finish
|
|
checkButton(tester);
|
|
});
|
|
|
|
testWidgets('Button theme style overrides defaults, empty button and overall styles', (WidgetTester tester) async {
|
|
await tester.pumpWidget(buildFrame(buttonStyle: const ButtonStyle(), themeStyle: style, overallStyle: const ButtonStyle()));
|
|
await tester.pumpAndSettle(); // allow the animations to finish
|
|
checkButton(tester);
|
|
});
|
|
|
|
testWidgets('Overall Theme button theme style overrides defaults, null theme and empty overall style', (WidgetTester tester) async {
|
|
await tester.pumpWidget(buildFrame(buttonStyle: const ButtonStyle(), overallStyle: style));
|
|
await tester.pumpAndSettle(); // allow the animations to finish
|
|
checkButton(tester);
|
|
});
|
|
});
|
|
|
|
testWidgets('Theme shadowColor', (WidgetTester tester) async {
|
|
const ColorScheme colorScheme = ColorScheme.light();
|
|
const Color shadowColor = Color(0xff000001);
|
|
const Color overriddenColor = Color(0xff000002);
|
|
|
|
Widget buildFrame({ Color? overallShadowColor, Color? themeShadowColor, Color? shadowColor }) {
|
|
return MaterialApp(
|
|
theme: ThemeData.from(colorScheme: colorScheme, useMaterial3: true).copyWith(
|
|
shadowColor: overallShadowColor,
|
|
),
|
|
home: Scaffold(
|
|
body: Center(
|
|
child: IconButtonTheme(
|
|
data: IconButtonThemeData(
|
|
style: IconButton.styleFrom(
|
|
shadowColor: themeShadowColor,
|
|
),
|
|
),
|
|
child: Builder(
|
|
builder: (BuildContext context) {
|
|
return IconButton(
|
|
style: IconButton.styleFrom(
|
|
shadowColor: shadowColor,
|
|
),
|
|
onPressed: () { },
|
|
icon: const Icon(Icons.add),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
final Finder buttonMaterialFinder = find.descendant(
|
|
of: find.byType(IconButton),
|
|
matching: find.byType(Material),
|
|
);
|
|
|
|
await tester.pumpWidget(buildFrame());
|
|
Material material = tester.widget<Material>(buttonMaterialFinder);
|
|
expect(material.shadowColor, Colors.transparent); //default
|
|
|
|
await tester.pumpWidget(buildFrame(overallShadowColor: shadowColor));
|
|
await tester.pumpAndSettle(); // theme animation
|
|
material = tester.widget<Material>(buttonMaterialFinder);
|
|
expect(material.shadowColor, Colors.transparent);
|
|
|
|
await tester.pumpWidget(buildFrame(themeShadowColor: shadowColor));
|
|
await tester.pumpAndSettle(); // theme animation
|
|
material = tester.widget<Material>(buttonMaterialFinder);
|
|
expect(material.shadowColor, shadowColor);
|
|
|
|
await tester.pumpWidget(buildFrame(shadowColor: shadowColor));
|
|
await tester.pumpAndSettle(); // theme animation
|
|
material = tester.widget<Material>(buttonMaterialFinder);
|
|
expect(material.shadowColor, shadowColor);
|
|
|
|
await tester.pumpWidget(buildFrame(overallShadowColor: overriddenColor, themeShadowColor: shadowColor));
|
|
await tester.pumpAndSettle(); // theme animation
|
|
material = tester.widget<Material>(buttonMaterialFinder);
|
|
expect(material.shadowColor, shadowColor);
|
|
|
|
await tester.pumpWidget(buildFrame(themeShadowColor: overriddenColor, shadowColor: shadowColor));
|
|
await tester.pumpAndSettle(); // theme animation
|
|
material = tester.widget<Material>(buttonMaterialFinder);
|
|
expect(material.shadowColor, shadowColor);
|
|
});
|
|
|
|
testWidgets('IconButtonTheme IconButton.styleFrom overlayColor overrides default overlay color', (WidgetTester tester) async {
|
|
const Color overlayColor = Color(0xffff0000);
|
|
await tester.pumpWidget(
|
|
MaterialApp(
|
|
home: Scaffold(
|
|
body: Center(
|
|
child: IconButtonTheme(
|
|
data: IconButtonThemeData(
|
|
style: IconButton.styleFrom(
|
|
overlayColor: overlayColor,
|
|
),
|
|
),
|
|
child: IconButton(
|
|
onPressed: () { },
|
|
icon: const Icon(Icons.add),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
|
|
// Hovered.
|
|
final Offset center = tester.getCenter(find.byType(IconButton));
|
|
final TestGesture gesture = await tester.createGesture(
|
|
kind: PointerDeviceKind.mouse,
|
|
);
|
|
await gesture.addPointer();
|
|
await gesture.moveTo(center);
|
|
await tester.pumpAndSettle();
|
|
expect(getOverlayColor(tester), paints..rect(color: overlayColor.withOpacity(0.08)));
|
|
|
|
// Highlighted (pressed).
|
|
await gesture.down(center);
|
|
await tester.pumpAndSettle();
|
|
expect(
|
|
getOverlayColor(tester),
|
|
paints
|
|
..rect(color: overlayColor.withOpacity(0.08))
|
|
..rect(color: overlayColor.withOpacity(0.1)),
|
|
);
|
|
// Remove pressed and hovered states,
|
|
await gesture.up();
|
|
await tester.pumpAndSettle();
|
|
await gesture.moveTo(const Offset(0, 50));
|
|
await tester.pumpAndSettle();
|
|
|
|
// Focused.
|
|
await tester.sendKeyEvent(LogicalKeyboardKey.tab);
|
|
await tester.pumpAndSettle();
|
|
expect(getOverlayColor(tester), paints..rect(color: overlayColor.withOpacity(0.1)));
|
|
});
|
|
}
|