blob: 4fd4bfd07554caebc4e1a61b7c0550f0e285848b [file] [log] [blame]
// 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 'dart:ui';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter_test/flutter_test.dart';
import '../rendering/mock_canvas.dart';
import '../widgets/semantics_tester.dart';
import 'feedback_tester.dart';
class MockOnPressedFunction {
int called = 0;
void handler() {
called++;
}
}
void main() {
late MockOnPressedFunction mockOnPressedFunction;
const ColorScheme colorScheme = ColorScheme.light();
final ThemeData theme = ThemeData.from(colorScheme: colorScheme);
setUp(() {
mockOnPressedFunction = MockOnPressedFunction();
});
testWidgets('test icon is findable by key', (WidgetTester tester) async {
const ValueKey<String> key = ValueKey<String>('icon-button');
await tester.pumpWidget(
wrap(
useMaterial3: true,
child: IconButton(
key: key,
onPressed: () {},
icon: const Icon(Icons.link),
),
),
);
expect(find.byKey(key), findsOneWidget);
});
testWidgets('test default icon buttons are sized up to 48', (WidgetTester tester) async {
final bool material3 = theme.useMaterial3;
await tester.pumpWidget(
wrap(
useMaterial3: material3,
child: IconButton(
onPressed: mockOnPressedFunction.handler,
icon: const Icon(Icons.link),
),
),
);
final RenderBox iconButton = tester.renderObject(find.byType(IconButton));
expect(iconButton.size, const Size(48.0, 48.0));
await tester.tap(find.byType(IconButton));
expect(mockOnPressedFunction.called, 1);
});
testWidgets('test small icons are sized up to 48dp', (WidgetTester tester) async {
final bool material3 = theme.useMaterial3;
await tester.pumpWidget(
wrap(
useMaterial3: material3,
child: IconButton(
iconSize: 10.0,
onPressed: mockOnPressedFunction.handler,
icon: const Icon(Icons.link),
),
)
);
final RenderBox iconButton = tester.renderObject(find.byType(IconButton));
expect(iconButton.size, const Size(48.0, 48.0));
});
testWidgets('test icons can be small when total size is >48dp', (WidgetTester tester) async {
final bool material3 = theme.useMaterial3;
await tester.pumpWidget(
wrap(
useMaterial3: material3,
child: IconButton(
iconSize: 10.0,
padding: const EdgeInsets.all(30.0),
onPressed: mockOnPressedFunction.handler,
icon: const Icon(Icons.link),
),
)
);
final RenderBox iconButton = tester.renderObject(find.byType(IconButton));
expect(iconButton.size, const Size(70.0, 70.0));
});
testWidgets('when both iconSize and IconTheme.of(context).size are null, size falls back to 24.0', (WidgetTester tester) async {
final bool material3 = theme.useMaterial3;
await tester.pumpWidget(
wrap(
useMaterial3: material3,
child: IconTheme(
data: const IconThemeData(),
child: IconButton(
focusNode: FocusNode(debugLabel: 'Ink Focus'),
onPressed: mockOnPressedFunction.handler,
icon: const Icon(Icons.link),
),
)
)
);
final RenderBox icon = tester.renderObject(find.byType(Icon));
expect(icon.size, const Size(24.0, 24.0));
});
testWidgets('when null, iconSize is overridden by closest IconTheme', (WidgetTester tester) async {
RenderBox icon;
final bool material3 = theme.useMaterial3;
await tester.pumpWidget(
wrap(
useMaterial3: material3,
child: IconTheme(
data: const IconThemeData(size: 10),
child: IconButton(
onPressed: mockOnPressedFunction.handler,
icon: const Icon(Icons.link),
),
)
)
);
icon = tester.renderObject(find.byType(Icon));
expect(icon.size, const Size(10.0, 10.0));
await tester.pumpWidget(
wrap(
useMaterial3: material3,
child: Theme(
data: ThemeData(
useMaterial3: material3,
iconTheme: const IconThemeData(size: 10),
),
child: IconButton(
onPressed: mockOnPressedFunction.handler,
icon: const Icon(Icons.link),
),
)
)
);
icon = tester.renderObject(find.byType(Icon));
expect(icon.size, const Size(10.0, 10.0));
await tester.pumpWidget(
wrap(
useMaterial3: material3,
child: Theme(
data: ThemeData(
useMaterial3: material3,
iconTheme: const IconThemeData(size: 20),
),
child: IconTheme(
data: const IconThemeData(size: 10),
child: IconButton(
onPressed: mockOnPressedFunction.handler,
icon: const Icon(Icons.link),
),
),
)
),
);
icon = tester.renderObject(find.byType(Icon));
expect(icon.size, const Size(10.0, 10.0));
await tester.pumpWidget(
wrap(
useMaterial3: material3,
child: IconTheme(
data: const IconThemeData(size: 20),
child: Theme(
data: ThemeData(
useMaterial3: material3,
iconTheme: const IconThemeData(size: 10),
),
child: IconButton(
onPressed: mockOnPressedFunction.handler,
icon: const Icon(Icons.link),
),
),
)
),
);
icon = tester.renderObject(find.byType(Icon));
expect(icon.size, const Size(10.0, 10.0));
});
testWidgets('when non-null, iconSize precedes IconTheme.of(context).size', (WidgetTester tester) async {
final bool material3 = theme.useMaterial3;
await tester.pumpWidget(
wrap(
useMaterial3: material3,
child: IconTheme(
data: const IconThemeData(size: 30.0),
child: IconButton(
iconSize: 10.0,
onPressed: mockOnPressedFunction.handler,
icon: const Icon(Icons.link),
),
)
),
);
final RenderBox icon = tester.renderObject(find.byType(Icon));
expect(icon.size, const Size(10.0, 10.0));
});
testWidgets('Small icons with non-null constraints can be <48dp for M2, but =48dp for M3', (WidgetTester tester) async {
final bool material3 = theme.useMaterial3;
await tester.pumpWidget(
wrap(
useMaterial3: material3,
child: IconButton(
iconSize: 10.0,
onPressed: mockOnPressedFunction.handler,
icon: const Icon(Icons.link),
constraints: const BoxConstraints(),
)
)
);
final RenderBox iconButton = tester.renderObject(find.byType(IconButton));
final RenderBox icon = tester.renderObject(find.byType(Icon));
// By default IconButton has a padding of 8.0 on all sides, so both
// width and height are 10.0 + 2 * 8.0 = 26.0
// M3 IconButton is a subclass of ButtonStyleButton which has a minimum
// Size(48.0, 48.0).
expect(iconButton.size, material3 ? const Size(48.0, 48.0) : const Size(26.0, 26.0));
expect(icon.size, const Size(10.0, 10.0));
});
testWidgets('Small icons with non-null constraints and custom padding can be <48dp', (WidgetTester tester) async {
final bool material3 = theme.useMaterial3;
await tester.pumpWidget(
wrap(
useMaterial3: material3,
child: IconButton(
iconSize: 10.0,
padding: const EdgeInsets.all(3.0),
onPressed: mockOnPressedFunction.handler,
icon: const Icon(Icons.link),
constraints: const BoxConstraints(),
),
),
);
final RenderBox iconButton = tester.renderObject(find.byType(IconButton));
final RenderBox icon = tester.renderObject(find.byType(Icon));
// This IconButton has a padding of 3.0 on all sides, so both
// width and height are 10.0 + 2 * 3.0 = 16.0
// M3 IconButton is a subclass of ButtonStyleButton which has a minimum
// Size(48.0, 48.0).
expect(iconButton.size, material3 ? const Size(48.0, 48.0) : const Size(16.0, 16.0));
expect(icon.size, const Size(10.0, 10.0));
});
testWidgets('Small icons comply with VisualDensity requirements', (WidgetTester tester) async {
final bool material3 = theme.useMaterial3;
final ThemeData themeDataM2 = ThemeData(
useMaterial3: material3,
visualDensity: const VisualDensity(horizontal: 1, vertical: -1),
);
final ThemeData themeDataM3 = ThemeData(
useMaterial3: material3,
iconButtonTheme: IconButtonThemeData(
style: IconButton.styleFrom(
visualDensity: const VisualDensity(horizontal: 1, vertical: -1)
)
),
);
await tester.pumpWidget(
wrap(
useMaterial3: material3,
child: Theme(
data: material3 ? themeDataM3 : themeDataM2,
child: IconButton(
iconSize: 10.0,
onPressed: mockOnPressedFunction.handler,
icon: const Icon(Icons.link),
constraints: const BoxConstraints(minWidth: 32.0, minHeight: 32.0),
),
),
),
);
final RenderBox iconButton = tester.renderObject(find.byType(IconButton));
// VisualDensity(horizontal: 1, vertical: -1) increases the icon's
// width by 4 pixels and decreases its height by 4 pixels, giving
// final width 32.0 + 4.0 = 36.0 and
// final height 32.0 - 4.0 = 28.0
expect(iconButton.size, material3 ? const Size(52.0, 44.0) : const Size(36.0, 28.0));
});
testWidgets('test default icon buttons are constrained', (WidgetTester tester) async {
await tester.pumpWidget(
wrap(
useMaterial3: theme.useMaterial3,
child: IconButton(
padding: EdgeInsets.zero,
onPressed: mockOnPressedFunction.handler,
icon: const Icon(Icons.ac_unit),
iconSize: 80.0,
),
),
);
final RenderBox box = tester.renderObject(find.byType(IconButton));
expect(box.size, const Size(80.0, 80.0));
});
testWidgets('test default icon buttons can be stretched if specified', (WidgetTester tester) async {
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: Material(
child: Row(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget> [
IconButton(
onPressed: mockOnPressedFunction.handler,
icon: const Icon(Icons.ac_unit),
),
],
),
),
),
);
final RenderBox box = tester.renderObject(find.byType(IconButton));
expect(box.size, const Size(48.0, 600.0));
// Test for Material 3
await tester.pumpWidget(
MaterialApp(
theme: ThemeData.from(colorScheme: colorScheme, useMaterial3: true),
home: Directionality(
textDirection: TextDirection.ltr,
child: Row(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget> [
IconButton(
onPressed: mockOnPressedFunction.handler,
icon: const Icon(Icons.ac_unit),
),
],
),
),
)
);
final RenderBox boxM3 = tester.renderObject(find.byType(IconButton));
expect(boxM3.size, const Size(48.0, 600.0));
});
testWidgets('test default padding', (WidgetTester tester) async {
await tester.pumpWidget(
wrap(
useMaterial3: theme.useMaterial3,
child: IconButton(
onPressed: mockOnPressedFunction.handler,
icon: const Icon(Icons.ac_unit),
iconSize: 80.0,
),
),
);
final RenderBox box = tester.renderObject(find.byType(IconButton));
expect(box.size, const Size(96.0, 96.0));
});
testWidgets('test default alignment', (WidgetTester tester) async {
await tester.pumpWidget(
wrap(
useMaterial3: theme.useMaterial3,
child: IconButton(
onPressed: mockOnPressedFunction.handler,
icon: const Icon(Icons.ac_unit),
iconSize: 80.0,
),
),
);
final Align align = tester.firstWidget<Align>(find.ancestor(of: find.byIcon(Icons.ac_unit), matching: find.byType(Align)));
expect(align.alignment, Alignment.center);
});
testWidgets('test tooltip', (WidgetTester tester) async {
await tester.pumpWidget(
MaterialApp(
theme: theme,
home: Material(
child: Center(
child: IconButton(
onPressed: mockOnPressedFunction.handler,
icon: const Icon(Icons.ac_unit),
),
),
),
),
);
expect(find.byType(Tooltip), findsNothing);
// Clear the widget tree.
await tester.pumpWidget(Container(key: UniqueKey()));
await tester.pumpWidget(
MaterialApp(
theme: theme,
home: Material(
child: Center(
child: IconButton(
onPressed: mockOnPressedFunction.handler,
icon: const Icon(Icons.ac_unit),
tooltip: 'Test tooltip',
),
),
),
),
);
expect(find.byType(Tooltip), findsOneWidget);
expect(find.byTooltip('Test tooltip'), findsOneWidget);
await tester.tap(find.byTooltip('Test tooltip'));
expect(mockOnPressedFunction.called, 1);
});
testWidgets('IconButton AppBar size', (WidgetTester tester) async {
final bool material3 = theme.useMaterial3;
await tester.pumpWidget(
MaterialApp(
theme: theme,
home: Scaffold(
appBar: AppBar(
actions: <Widget>[
IconButton(
padding: EdgeInsets.zero,
onPressed: mockOnPressedFunction.handler,
icon: const Icon(Icons.ac_unit),
),
],
),
),
),
);
final RenderBox barBox = tester.renderObject(find.byType(AppBar));
final RenderBox iconBox = tester.renderObject(find.byType(IconButton));
expect(iconBox.size.height, material3 ? 48 : equals(barBox.size.height));
expect(tester.getCenter(find.byType(IconButton)).dy, 28);
});
// This test is very similar to the '...explicit splashColor and highlightColor' test
// in buttons_test.dart. If you change this one, you may want to also change that one.
testWidgets('IconButton with explicit splashColor and highlightColor - M2', (WidgetTester tester) async {
const Color directSplashColor = Color(0xFF00000F);
const Color directHighlightColor = Color(0xFF0000F0);
Widget buttonWidget = wrap(
useMaterial3: false,
child: IconButton(
icon: const Icon(Icons.android),
splashColor: directSplashColor,
highlightColor: directHighlightColor,
onPressed: () { /* enable the button */ },
),
);
await tester.pumpWidget(
Theme(
data: ThemeData(useMaterial3: false),
child: buttonWidget,
),
);
final Offset center = tester.getCenter(find.byType(IconButton));
final TestGesture gesture = await tester.startGesture(center);
await tester.pump(); // start gesture
await tester.pump(const Duration(milliseconds: 200)); // wait for splash to be well under way
expect(
Material.of(tester.element(find.byType(IconButton))),
paints
..circle(color: directSplashColor)
..circle(color: directHighlightColor),
);
const Color themeSplashColor1 = Color(0xFF000F00);
const Color themeHighlightColor1 = Color(0xFF00FF00);
buttonWidget = wrap(
useMaterial3: false,
child: IconButton(
icon: const Icon(Icons.android),
onPressed: () { /* enable the button */ },
),
);
await tester.pumpWidget(
Theme(
data: ThemeData(
highlightColor: themeHighlightColor1,
splashColor: themeSplashColor1,
useMaterial3: false,
),
child: buttonWidget,
),
);
expect(
Material.of(tester.element(find.byType(IconButton))),
paints
..circle(color: themeSplashColor1)
..circle(color: themeHighlightColor1),
);
const Color themeSplashColor2 = Color(0xFF002200);
const Color themeHighlightColor2 = Color(0xFF001100);
await tester.pumpWidget(
Theme(
data: ThemeData(
highlightColor: themeHighlightColor2,
splashColor: themeSplashColor2,
useMaterial3: false,
),
child: buttonWidget, // same widget, so does not get updated because of us
),
);
expect(
Material.of(tester.element(find.byType(IconButton))),
paints
..circle(color: themeSplashColor2)
..circle(color: themeHighlightColor2),
);
await gesture.up();
});
testWidgets('IconButton with explicit splash radius - M2', (WidgetTester tester) async {
const double splashRadius = 30.0;
await tester.pumpWidget(
MaterialApp(
theme: ThemeData(useMaterial3: false),
home: Material(
child: Center(
child: IconButton(
icon: const Icon(Icons.android),
splashRadius: splashRadius,
onPressed: () { /* enable the button */ },
),
),
),
),
);
final Offset center = tester.getCenter(find.byType(IconButton));
final TestGesture gesture = await tester.startGesture(center);
await tester.pump(); // Start gesture.
await tester.pump(const Duration(milliseconds: 1000)); // Wait for splash to be well under way.
expect(
Material.of(tester.element(find.byType(IconButton))),
paints
..circle(radius: splashRadius),
);
await gesture.up();
});
testWidgets('IconButton Semantics (enabled) - M2', (WidgetTester tester) async {
final SemanticsTester semantics = SemanticsTester(tester);
await tester.pumpWidget(
wrap(
useMaterial3: false,
child: IconButton(
onPressed: mockOnPressedFunction.handler,
icon: const Icon(Icons.link, semanticLabel: 'link'),
),
),
);
expect(semantics, hasSemantics(TestSemantics.root(
children: <TestSemantics>[
TestSemantics.rootChild(
rect: const Rect.fromLTRB(0.0, 0.0, 48.0, 48.0),
actions: <SemanticsAction>[
SemanticsAction.tap,
],
flags: <SemanticsFlag>[
SemanticsFlag.hasEnabledState,
SemanticsFlag.isButton,
SemanticsFlag.isEnabled,
SemanticsFlag.isFocusable,
],
label: 'link',
),
],
), ignoreId: true, ignoreTransform: true));
semantics.dispose();
});
testWidgets('IconButton Semantics (disabled) - M2', (WidgetTester tester) async {
final SemanticsTester semantics = SemanticsTester(tester);
await tester.pumpWidget(
wrap(
useMaterial3: false,
child: const IconButton(
onPressed: null,
icon: Icon(Icons.link, semanticLabel: 'link'),
),
),
);
expect(semantics, hasSemantics(TestSemantics.root(
children: <TestSemantics>[
TestSemantics.rootChild(
rect: const Rect.fromLTRB(0.0, 0.0, 48.0, 48.0),
flags: <SemanticsFlag>[
SemanticsFlag.hasEnabledState,
SemanticsFlag.isButton,
],
label: 'link',
),
],
), ignoreId: true, ignoreTransform: true));
semantics.dispose();
});
testWidgets('IconButton loses focus when disabled.', (WidgetTester tester) async {
final FocusNode focusNode = FocusNode(debugLabel: 'IconButton');
await tester.pumpWidget(
wrap(
useMaterial3: theme.useMaterial3,
child: IconButton(
focusNode: focusNode,
autofocus: true,
onPressed: () {},
icon: const Icon(Icons.link),
),
),
);
await tester.pump();
expect(focusNode.hasPrimaryFocus, isTrue);
await tester.pumpWidget(
wrap(
useMaterial3: theme.useMaterial3,
child: IconButton(
focusNode: focusNode,
autofocus: true,
onPressed: null,
icon: const Icon(Icons.link),
),
),
);
await tester.pump();
expect(focusNode.hasPrimaryFocus, isFalse);
});
testWidgets('IconButton keeps focus when disabled in directional navigation mode.', (WidgetTester tester) async {
final FocusNode focusNode = FocusNode(debugLabel: 'IconButton');
await tester.pumpWidget(
wrap(
useMaterial3: theme.useMaterial3,
child: MediaQuery(
data: const MediaQueryData(
navigationMode: NavigationMode.directional,
),
child: IconButton(
focusNode: focusNode,
autofocus: true,
onPressed: () {},
icon: const Icon(Icons.link),
),
),
),
);
await tester.pump();
expect(focusNode.hasPrimaryFocus, isTrue);
await tester.pumpWidget(
wrap(
useMaterial3: theme.useMaterial3,
child: MediaQuery(
data: const MediaQueryData(
navigationMode: NavigationMode.directional,
),
child: IconButton(
focusNode: focusNode,
autofocus: true,
onPressed: null,
icon: const Icon(Icons.link),
),
),
),
);
await tester.pump();
expect(focusNode.hasPrimaryFocus, isTrue);
});
testWidgets("Disabled IconButton can't be traversed to when disabled.", (WidgetTester tester) async {
final FocusNode focusNode1 = FocusNode(debugLabel: 'IconButton 1');
final FocusNode focusNode2 = FocusNode(debugLabel: 'IconButton 2');
await tester.pumpWidget(
wrap(
useMaterial3: theme.useMaterial3,
child: Column(
children: <Widget>[
IconButton(
focusNode: focusNode1,
autofocus: true,
onPressed: () {},
icon: const Icon(Icons.link),
),
IconButton(
focusNode: focusNode2,
onPressed: null,
icon: const Icon(Icons.link),
),
],
),
),
);
await tester.pump();
expect(focusNode1.hasPrimaryFocus, isTrue);
expect(focusNode2.hasPrimaryFocus, isFalse);
expect(focusNode1.nextFocus(), isTrue);
await tester.pump();
expect(focusNode1.hasPrimaryFocus, isTrue);
expect(focusNode2.hasPrimaryFocus, isFalse);
});
group('feedback', () {
late FeedbackTester feedback;
setUp(() {
feedback = FeedbackTester();
});
tearDown(() {
feedback.dispose();
});
testWidgets('IconButton with disabled feedback', (WidgetTester tester) async {
final Widget button = Directionality(
textDirection: TextDirection.ltr,
child: Center(
child: IconButton(
onPressed: () {},
enableFeedback: false,
icon: const Icon(Icons.link),
),
),
);
await tester.pumpWidget(
theme.useMaterial3
? MaterialApp(theme: theme, home: button)
: Material(child: button)
);
await tester.tap(find.byType(IconButton), pointer: 1);
await tester.pump(const Duration(seconds: 1));
expect(feedback.clickSoundCount, 0);
expect(feedback.hapticCount, 0);
});
testWidgets('IconButton with enabled feedback', (WidgetTester tester) async {
final Widget button = Directionality(
textDirection: TextDirection.ltr,
child: Center(
child: IconButton(
onPressed: () {},
icon: const Icon(Icons.link),
),
),
);
await tester.pumpWidget(
theme.useMaterial3
? MaterialApp(theme: theme, home: button)
: Material(child: button),
);
await tester.tap(find.byType(IconButton), pointer: 1);
await tester.pump(const Duration(seconds: 1));
expect(feedback.clickSoundCount, 1);
expect(feedback.hapticCount, 0);
});
testWidgets('IconButton with enabled feedback by default', (WidgetTester tester) async {
final Widget button = Directionality(
textDirection: TextDirection.ltr,
child: Center(
child: IconButton(
onPressed: () {},
icon: const Icon(Icons.link),
),
),
);
await tester.pumpWidget(
theme.useMaterial3
? MaterialApp(theme: theme, home: button)
: Material(child: button),
);
await tester.tap(find.byType(IconButton), pointer: 1);
await tester.pump(const Duration(seconds: 1));
expect(feedback.clickSoundCount, 1);
expect(feedback.hapticCount, 0);
});
});
testWidgets('IconButton responds to density changes.', (WidgetTester tester) async {
const Key key = Key('test');
final bool material3 = theme.useMaterial3;
Future<void> buildTest(VisualDensity visualDensity) async {
return tester.pumpWidget(
MaterialApp(
theme: theme,
home: Material(
child: Center(
child: IconButton(
visualDensity: visualDensity,
key: key,
onPressed: () {},
icon: const Icon(Icons.play_arrow),
),
),
),
),
);
}
await buildTest(VisualDensity.standard);
final RenderBox box = tester.renderObject(find.byType(IconButton));
await tester.pumpAndSettle();
expect(box.size, equals(const Size(48, 48)));
await buildTest(const VisualDensity(horizontal: 3.0, vertical: 3.0));
await tester.pumpAndSettle();
expect(box.size, equals(material3 ? const Size(64, 64) : const Size(60, 60)));
await buildTest(const VisualDensity(horizontal: -3.0, vertical: -3.0));
await tester.pumpAndSettle();
// IconButton is a subclass of ButtonStyleButton in Material 3, so the negative
// visualDensity cannot be applied to horizontal padding.
// The size of the Button with padding is (24 + 8 + 8, 24) -> (40, 24)
// minSize of M3 IconButton is (48 - 12, 48 - 12) -> (36, 36)
// So, the button size in Material 3 is (40, 36)
expect(box.size, equals(material3 ? const Size(40, 36) : const Size(40, 40)));
await buildTest(const VisualDensity(horizontal: 3.0, vertical: -3.0));
await tester.pumpAndSettle();
expect(box.size, equals(material3 ? const Size(64, 36) : const Size(60, 40)));
});
testWidgets('IconButton.mouseCursor changes cursor on hover', (WidgetTester tester) async {
// Test argument works
await tester.pumpWidget(
MaterialApp(
theme: theme,
home: Material(
child: Directionality(
textDirection: TextDirection.ltr,
child: Center(
child: IconButton(
onPressed: () {},
mouseCursor: SystemMouseCursors.forbidden,
icon: const Icon(Icons.play_arrow),
),
),
),
),
),
);
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse, pointer: 1);
await gesture.addPointer(location: tester.getCenter(find.byType(IconButton)));
await tester.pump();
expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.forbidden);
// Test default is click
await tester.pumpWidget(
MaterialApp(
theme: theme,
home: Material(
child: Directionality(
textDirection: TextDirection.ltr,
child: Center(
child: IconButton(
onPressed: () {},
icon: const Icon(Icons.play_arrow),
),
),
),
),
),
);
expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.click);
});
testWidgets('disabled IconButton has basic mouse cursor', (WidgetTester tester) async {
await tester.pumpWidget(
MaterialApp(
theme: theme,
home: const Material(
child: Directionality(
textDirection: TextDirection.ltr,
child: Center(
child: IconButton(
onPressed: null, // null value indicates IconButton is disabled
icon: Icon(Icons.play_arrow),
),
),
),
),
),
);
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse, pointer: 1);
await gesture.addPointer(location: tester.getCenter(find.byType(IconButton)));
await tester.pump();
expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.basic);
});
testWidgets('IconButton.mouseCursor overrides implicit setting of mouse cursor', (WidgetTester tester) async {
await tester.pumpWidget(
MaterialApp(
theme: theme,
home: const Material(
child: Directionality(
textDirection: TextDirection.ltr,
child: Center(
child: IconButton(
onPressed: null,
mouseCursor: SystemMouseCursors.none,
icon: Icon(Icons.play_arrow),
),
),
),
),
),
);
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse, pointer: 1);
await gesture.addPointer(location: tester.getCenter(find.byType(IconButton)));
await tester.pump();
expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.none);
await tester.pumpWidget(
MaterialApp(
theme: theme,
home: Material(
child: Directionality(
textDirection: TextDirection.ltr,
child: Center(
child: IconButton(
onPressed: () {},
mouseCursor: SystemMouseCursors.none,
icon: const Icon(Icons.play_arrow),
),
),
),
),
),
);
expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.none);
});
testWidgets('IconTheme opacity test', (WidgetTester tester) async {
final ThemeData theme = ThemeData.from(colorScheme: colorScheme, useMaterial3: false);
await tester.pumpWidget(
MaterialApp(
theme: theme,
home: Scaffold(
body: Center(
child: IconButton(
icon: const Icon(Icons.add),
color: Colors.purple,
onPressed: () {},
)
),
),
)
);
Color? iconColor() => _iconStyle(tester, Icons.add)?.color;
expect(iconColor(), Colors.purple);
await tester.pumpWidget(
MaterialApp(
theme: theme,
home: Scaffold(
body: Center(
child: IconTheme.merge(
data: const IconThemeData(opacity: 0.5),
child: IconButton(
icon: const Icon(Icons.add),
color: Colors.purple,
onPressed: () {},
),
)
),
),
)
);
Color? iconColorWithOpacity() => _iconStyle(tester, Icons.add)?.color;
expect(iconColorWithOpacity(), Colors.purple.withOpacity(0.5));
});
testWidgets('IconButton defaults - M3', (WidgetTester tester) async {
final ThemeData themeM3 = ThemeData.from(colorScheme: colorScheme, useMaterial3: true);
// Enabled IconButton
await tester.pumpWidget(
MaterialApp(
theme: themeM3,
home: Center(
child: IconButton(
onPressed: () { },
icon: const Icon(Icons.ac_unit),
),
),
),
);
final Finder buttonMaterial = find.descendant(
of: find.byType(IconButton),
matching: find.byType(Material),
);
Material material = tester.widget<Material>(buttonMaterial);
expect(material.animationDuration, const Duration(milliseconds: 200));
expect(material.borderOnForeground, true);
expect(material.borderRadius, null);
expect(material.clipBehavior, Clip.none);
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);
expect(tester.getSize(find.byIcon(Icons.ac_unit)), const Size(24.0, 24.0));
final Offset center = tester.getCenter(find.byType(IconButton));
final TestGesture gesture = await tester.startGesture(center);
await tester.pump(); // start the splash animation
await tester.pump(const Duration(milliseconds: 100)); // splash is underway
await gesture.up();
await tester.pumpAndSettle();
material = tester.widget<Material>(buttonMaterial);
// No change vs enabled and not pressed.
expect(material.animationDuration, const Duration(milliseconds: 200));
expect(material.borderOnForeground, true);
expect(material.borderRadius, null);
expect(material.clipBehavior, Clip.none);
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);
// Disabled IconButton
await tester.pumpWidget(
MaterialApp(
theme: themeM3,
home: const Center(
child: IconButton(
onPressed: null,
icon: Icon(Icons.ac_unit),
),
),
),
);
material = tester.widget<Material>(buttonMaterial);
expect(material.animationDuration, const Duration(milliseconds: 200));
expect(material.borderOnForeground, true);
expect(material.borderRadius, null);
expect(material.clipBehavior, Clip.none);
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);
});
testWidgets('Default IconButton meets a11y contrast guidelines - M3', (WidgetTester tester) async {
final FocusNode focusNode = FocusNode();
await tester.pumpWidget(
MaterialApp(
theme: ThemeData.from(colorScheme: const ColorScheme.light(), useMaterial3: true),
home: Scaffold(
body: Center(
child: IconButton(
onPressed: () { },
focusNode: focusNode,
icon: const Icon(Icons.ac_unit),
),
),
),
),
);
// Default, not disabled.
await expectLater(tester, meetsGuideline(textContrastGuideline));
// Focused.
focusNode.requestFocus();
await tester.pumpAndSettle();
await expectLater(tester, meetsGuideline(textContrastGuideline));
// 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();
await expectLater(tester, meetsGuideline(textContrastGuideline));
// 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.
await expectLater(tester, meetsGuideline(textContrastGuideline));
await gesture.removePointer();
},
skip: isBrowser, // https://github.com/flutter/flutter/issues/44115
);
testWidgets('IconButton uses stateful color for icon color in different states - M3', (WidgetTester tester) async {
bool isSelected = false;
final FocusNode focusNode = FocusNode();
const Color pressedColor = Color(0x00000001);
const Color hoverColor = Color(0x00000002);
const Color focusedColor = Color(0x00000003);
const Color defaultColor = Color(0x00000004);
const Color selectedColor = Color(0x00000005);
Color getIconColor(Set<MaterialState> states) {
if (states.contains(MaterialState.pressed)) {
return pressedColor;
}
if (states.contains(MaterialState.hovered)) {
return hoverColor;
}
if (states.contains(MaterialState.focused)) {
return focusedColor;
}
if (states.contains(MaterialState.selected)) {
return selectedColor;
}
return defaultColor;
}
await tester.pumpWidget(
MaterialApp(
theme: ThemeData.from(colorScheme: const ColorScheme.light(), useMaterial3: true),
home: StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return Scaffold(
body: Center(
child: IconButton(
style: ButtonStyle(
foregroundColor: MaterialStateProperty.resolveWith<Color>(getIconColor),
),
isSelected: isSelected,
onPressed: () {
setState(() {
isSelected = !isSelected;
});
},
focusNode: focusNode,
icon: const Icon(Icons.ac_unit),
),
),
);
}
),
),
);
Color? iconColor() => _iconStyle(tester, Icons.ac_unit)?.color;
// Default, not disabled.
expect(iconColor(), equals(defaultColor));
// Selected
final Finder button = find.byType(IconButton);
await tester.tap(button);
await tester.pumpAndSettle();
expect(iconColor(), selectedColor);
// Focused.
focusNode.requestFocus();
await tester.pumpAndSettle();
expect(iconColor(), focusedColor);
// 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(iconColor(), 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);
});
testWidgets('Does IconButton contribute semantics - M3', (WidgetTester tester) async {
final SemanticsTester semantics = SemanticsTester(tester);
await tester.pumpWidget(
Directionality(
textDirection: TextDirection.ltr,
child: Center(
child: Theme(
data: ThemeData(useMaterial3: true),
child: IconButton(
style: const ButtonStyle(
// Specifying minimumSize to mimic the original minimumSize for
// RaisedButton so that the semantics tree's rect and transform
// match the original version of this test.
minimumSize: MaterialStatePropertyAll<Size>(Size(88, 36)),
),
onPressed: () { },
icon: const Icon(Icons.ac_unit),
),
),
),
),
);
expect(semantics, hasSemantics(
TestSemantics.root(
children: <TestSemantics>[
TestSemantics.rootChild(
actions: <SemanticsAction>[
SemanticsAction.tap,
],
rect: const Rect.fromLTRB(0.0, 0.0, 88.0, 48.0),
transform: Matrix4.translationValues(356.0, 276.0, 0.0),
flags: <SemanticsFlag>[
SemanticsFlag.hasEnabledState,
SemanticsFlag.isButton,
SemanticsFlag.isEnabled,
SemanticsFlag.isFocusable,
],
),
],
),
ignoreId: true,
));
semantics.dispose();
});
testWidgets('IconButton size is configurable by ThemeData.materialTapTargetSize - M3', (WidgetTester tester) async {
Widget buildFrame(MaterialTapTargetSize tapTargetSize) {
return Theme(
data: ThemeData(materialTapTargetSize: tapTargetSize, useMaterial3: true),
child: Directionality(
textDirection: TextDirection.ltr,
child: Center(
child: IconButton(
style: IconButton.styleFrom(minimumSize: const Size(40, 40)),
icon: const Icon(Icons.ac_unit),
onPressed: () { },
),
),
),
);
}
await tester.pumpWidget(buildFrame(MaterialTapTargetSize.padded));
expect(tester.getSize(find.byType(IconButton)), const Size(48.0, 48.0));
await tester.pumpWidget(buildFrame(MaterialTapTargetSize.shrinkWrap));
expect(tester.getSize(find.byType(IconButton)), const Size(40.0, 40.0));
});
testWidgets('Override IconButton default padding - M3', (WidgetTester tester) async {
// Use [IconButton]'s padding property to override default value.
await tester.pumpWidget(
MaterialApp(
theme: ThemeData.from(colorScheme: const ColorScheme.light(), useMaterial3: true),
home: Scaffold(
body: Center(
child: IconButton(
padding: const EdgeInsets.all(20),
onPressed: () {},
icon: const Icon(Icons.ac_unit),
),
),
),
)
);
final Padding paddingWidget1 = tester.widget<Padding>(
find.descendant(
of: find.byType(IconButton),
matching: find.byType(Padding),
),
);
expect(paddingWidget1.padding, const EdgeInsets.all(20));
// Use [IconButton.style]'s padding property to override default value.
await tester.pumpWidget(
MaterialApp(
theme: ThemeData.from(colorScheme: const ColorScheme.light(), useMaterial3: true),
home: Scaffold(
body: Center(
child: IconButton(
style: IconButton.styleFrom(padding: const EdgeInsets.all(20)),
onPressed: () {},
icon: const Icon(Icons.ac_unit),
),
),
),
)
);
final Padding paddingWidget2 = tester.widget<Padding>(
find.descendant(
of: find.byType(IconButton),
matching: find.byType(Padding),
),
);
expect(paddingWidget2.padding, const EdgeInsets.all(20));
// [IconButton.style]'s padding will override [IconButton]'s padding if both
// values are not null.
await tester.pumpWidget(
MaterialApp(
theme: ThemeData.from(colorScheme: const ColorScheme.light(), useMaterial3: true),
home: Scaffold(
body: Center(
child: IconButton(
padding: const EdgeInsets.all(15),
style: IconButton.styleFrom(padding: const EdgeInsets.all(22)),
onPressed: () {},
icon: const Icon(Icons.ac_unit),
),
),
),
)
);
final Padding paddingWidget3 = tester.widget<Padding>(
find.descendant(
of: find.byType(IconButton),
matching: find.byType(Padding),
),
);
expect(paddingWidget3.padding, const EdgeInsets.all(22));
});
testWidgets('Default IconButton is not selectable - M3', (WidgetTester tester) async {
await tester.pumpWidget(
MaterialApp(
theme: ThemeData.from(colorScheme: const ColorScheme.light(), useMaterial3: true),
home: IconButton(icon: const Icon(Icons.ac_unit), onPressed: (){},)
)
);
final Finder button = find.byType(IconButton);
IconButton buttonWidget() => tester.widget<IconButton>(button);
Material buttonMaterial() {
return tester.widget<Material>(
find.descendant(
of: find.byType(IconButton),
matching: find.byType(Material),
)
);
}
Color? iconColor() => _iconStyle(tester, Icons.ac_unit)?.color;
expect(buttonWidget().isSelected, null);
expect(iconColor(), equals(const ColorScheme.light().onSurfaceVariant));
expect(buttonMaterial().color, Colors.transparent);
await tester.tap(button); // The non-toggle IconButton should not change appearance after clicking
await tester.pumpAndSettle();
expect(buttonWidget().isSelected, null);
expect(iconColor(), equals(const ColorScheme.light().onSurfaceVariant));
expect(buttonMaterial().color, Colors.transparent);
});
testWidgets('Icon button is selectable when isSelected is not null - M3', (WidgetTester tester) async {
bool isSelected = false;
await tester.pumpWidget(
MaterialApp(
theme: ThemeData.from(colorScheme: const ColorScheme.light(), useMaterial3: true),
home: StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return IconButton(
isSelected: isSelected,
icon: const Icon(Icons.ac_unit),
onPressed: (){
setState(() {
isSelected = !isSelected;
});
},
);
}
)
)
);
final Finder button = find.byType(IconButton);
IconButton buttonWidget() => tester.widget<IconButton>(button);
Color? iconColor() => _iconStyle(tester, Icons.ac_unit)?.color;
Material buttonMaterial() {
return tester.widget<Material>(
find.descendant(
of: find.byType(IconButton),
matching: find.byType(Material),
)
);
}
expect(buttonWidget().isSelected, false);
expect(iconColor(), equals(const ColorScheme.light().onSurfaceVariant));
expect(buttonMaterial().color, Colors.transparent);
await tester.tap(button); // The toggle IconButton should change appearance after clicking
await tester.pumpAndSettle();
expect(buttonWidget().isSelected, true);
expect(iconColor(), equals(const ColorScheme.light().primary));
expect(buttonMaterial().color, Colors.transparent);
await tester.tap(button); // The IconButton should be unselected if it's clicked again
await tester.pumpAndSettle();
expect(buttonWidget().isSelected, false);
expect(iconColor(), equals(const ColorScheme.light().onSurfaceVariant));
expect(buttonMaterial().color, Colors.transparent);
});
testWidgets('The IconButton is in selected status if isSelected is true by default - M3', (WidgetTester tester) async {
bool isSelected = true;
await tester.pumpWidget(
MaterialApp(
theme: ThemeData.from(colorScheme: const ColorScheme.light(), useMaterial3: true),
home: StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return IconButton(
isSelected: isSelected,
icon: const Icon(Icons.ac_unit),
onPressed: (){
setState(() {
isSelected = !isSelected;
});
},
);
}
)
)
);
final Finder button = find.byType(IconButton);
IconButton buttonWidget() => tester.widget<IconButton>(button);
Color? iconColor() => _iconStyle(tester, Icons.ac_unit)?.color;
Material buttonMaterial() {
return tester.widget<Material>(
find.descendant(
of: find.byType(IconButton),
matching: find.byType(Material),
)
);
}
expect(buttonWidget().isSelected, true);
expect(iconColor(), equals(const ColorScheme.light().primary));
expect(buttonMaterial().color, Colors.transparent);
await tester.tap(button); // The IconButton becomes unselected if it's clicked
await tester.pumpAndSettle();
expect(buttonWidget().isSelected, false);
expect(iconColor(), equals(const ColorScheme.light().onSurfaceVariant));
expect(buttonMaterial().color, Colors.transparent);
});
testWidgets("The selectedIcon is used if it's not null and the button is clicked" , (WidgetTester tester) async {
bool isSelected = false;
await tester.pumpWidget(
MaterialApp(
theme: ThemeData.from(colorScheme: const ColorScheme.light(), useMaterial3: true),
home: StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return IconButton(
isSelected: isSelected,
selectedIcon: const Icon(Icons.account_box),
icon: const Icon(Icons.account_box_outlined),
onPressed: (){
setState(() {
isSelected = !isSelected;
});
},
);
}
)
)
);
final Finder button = find.byType(IconButton);
expect(find.byIcon(Icons.account_box_outlined), findsOneWidget);
expect(find.byIcon(Icons.account_box), findsNothing);
await tester.tap(button); // The icon becomes to selectedIcon
await tester.pumpAndSettle();
expect(find.byIcon(Icons.account_box), findsOneWidget);
expect(find.byIcon(Icons.account_box_outlined), findsNothing);
await tester.tap(button); // The icon becomes the original icon when it's clicked again
await tester.pumpAndSettle();
expect(find.byIcon(Icons.account_box_outlined), findsOneWidget);
expect(find.byIcon(Icons.account_box), findsNothing);
});
testWidgets('The original icon is used for selected and unselected status when selectedIcon is null' , (WidgetTester tester) async {
bool isSelected = false;
await tester.pumpWidget(
MaterialApp(
theme: ThemeData.from(colorScheme: const ColorScheme.light(), useMaterial3: true),
home: StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
return IconButton(
isSelected: isSelected,
icon: const Icon(Icons.account_box),
onPressed: (){
setState(() {
isSelected = !isSelected;
});
},
);
}
)
)
);
final Finder button = find.byType(IconButton);
IconButton buttonWidget() => tester.widget<IconButton>(button);
expect(buttonWidget().isSelected, false);
expect(buttonWidget().selectedIcon, null);
expect(find.byIcon(Icons.account_box), findsOneWidget);
await tester.tap(button); // The icon becomes the original icon when it's clicked again
await tester.pumpAndSettle();
expect(buttonWidget().isSelected, true);
expect(buttonWidget().selectedIcon, null);
expect(find.byIcon(Icons.account_box), findsOneWidget);
});
testWidgets('The selectedIcon is used for disabled button if isSelected is true - M3' , (WidgetTester tester) async {
await tester.pumpWidget(
MaterialApp(
theme: ThemeData.from(colorScheme: const ColorScheme.light(), useMaterial3: true),
home: const IconButton(
isSelected: true,
icon: Icon(Icons.account_box),
selectedIcon: Icon(Icons.ac_unit),
onPressed: null,
)
)
);
final Finder button = find.byType(IconButton);
IconButton buttonWidget() => tester.widget<IconButton>(button);
expect(buttonWidget().isSelected, true);
expect(find.byIcon(Icons.account_box), findsNothing);
expect(find.byIcon(Icons.ac_unit), findsOneWidget);
});
testWidgets('The visualDensity of M3 IconButton can be configured by IconButtonTheme, '
'but cannot be configured by ThemeData - M3' , (WidgetTester tester) async {
Future<void> buildTest({VisualDensity? iconButtonThemeVisualDensity, VisualDensity? themeVisualDensity}) async {
return tester.pumpWidget(
MaterialApp(
theme: ThemeData.from(colorScheme: colorScheme, useMaterial3: true).copyWith(
iconButtonTheme: IconButtonThemeData(
style: IconButton.styleFrom(visualDensity: iconButtonThemeVisualDensity)
),
visualDensity: themeVisualDensity
),
home: Material(
child: Center(
child: IconButton(
onPressed: () {},
icon: const Icon(Icons.play_arrow),
),
),
),
),
);
}
await buildTest(iconButtonThemeVisualDensity: VisualDensity.standard);
final RenderBox box = tester.renderObject(find.byType(IconButton));
await tester.pumpAndSettle();
expect(box.size, equals(const Size(48, 48)));
await buildTest(iconButtonThemeVisualDensity: VisualDensity.compact);
await tester.pumpAndSettle();
expect(box.size, equals(const Size(40, 40)));
await buildTest(iconButtonThemeVisualDensity: const VisualDensity(horizontal: 3.0, vertical: 3.0));
await tester.pumpAndSettle();
expect(box.size, equals(const Size(64, 64)));
// ThemeData.visualDensity will be ignored because useMaterial3 is true
await buildTest(themeVisualDensity: VisualDensity.standard);
await tester.pumpAndSettle();
expect(box.size, equals(const Size(48, 48)));
await buildTest(themeVisualDensity: VisualDensity.compact);
await tester.pumpAndSettle();
expect(box.size, equals(const Size(48, 48)));
await buildTest(themeVisualDensity: const VisualDensity(horizontal: 3.0, vertical: 3.0));
await tester.pumpAndSettle();
expect(box.size, equals(const Size(48, 48)));
});
group('IconTheme tests in Material 3', () {
testWidgets('IconTheme overrides default values in M3', (WidgetTester tester) async {
// Theme's IconTheme
await tester.pumpWidget(
MaterialApp(
theme: ThemeData.from(
colorScheme: const ColorScheme.light(),
useMaterial3: true,
).copyWith(
iconTheme: const IconThemeData(color: Colors.red, size: 37),
),
home: IconButton(
icon: const Icon(Icons.account_box),
onPressed: () {},
)
)
);
Color? iconColor0() => _iconStyle(tester, Icons.account_box)?.color;
expect(iconColor0(), Colors.red);
expect(tester.getSize(find.byIcon(Icons.account_box)), equals(const Size(37, 37)),);
// custom IconTheme outside of IconButton
await tester.pumpWidget(
MaterialApp(
theme: ThemeData.from(
colorScheme: const ColorScheme.light(),
useMaterial3: true,
),
home: IconTheme.merge(
data: const IconThemeData(color: Colors.pink, size: 35),
child: IconButton(
icon: const Icon(Icons.account_box),
onPressed: () {},
),
)
)
);
Color? iconColor1() => _iconStyle(tester, Icons.account_box)?.color;
expect(iconColor1(), Colors.pink);
expect(tester.getSize(find.byIcon(Icons.account_box)), equals(const Size(35, 35)),);
});
testWidgets('Theme IconButtonTheme overrides IconTheme in Material3', (WidgetTester tester) async {
// When IconButtonTheme and IconTheme both exist in ThemeData, the IconButtonTheme can override IconTheme.
await tester.pumpWidget(
MaterialApp(
theme: ThemeData.from(
colorScheme: const ColorScheme.light(),
useMaterial3: true,
).copyWith(
iconTheme: const IconThemeData(color: Colors.red, size: 25),
iconButtonTheme: IconButtonThemeData(style: IconButton.styleFrom(foregroundColor: Colors.green, iconSize: 27),)
),
home: IconButton(
icon: const Icon(Icons.account_box),
onPressed: () {},
)
)
);
Color? iconColor() => _iconStyle(tester, Icons.account_box)?.color;
expect(iconColor(), Colors.green);
expect(tester.getSize(find.byIcon(Icons.account_box)), equals(const Size(27, 27)),);
});
testWidgets('Button IconButtonTheme always overrides IconTheme in Material3', (WidgetTester tester) async {
// When IconButtonTheme is closer to IconButton, IconButtonTheme overrides IconTheme
await tester.pumpWidget(
MaterialApp(
theme: ThemeData.from(
colorScheme: const ColorScheme.light(),
useMaterial3: true,
),
home: IconTheme.merge(
data: const IconThemeData(color: Colors.orange, size: 36),
child: IconButtonTheme(
data: IconButtonThemeData(style: IconButton.styleFrom(foregroundColor: Colors.blue, iconSize: 35)),
child: IconButton(
icon: const Icon(Icons.account_box),
onPressed: () {},
),
),
)
)
);
Color? iconColor0() => _iconStyle(tester, Icons.account_box)?.color;
expect(iconColor0(), Colors.blue);
expect(tester.getSize(find.byIcon(Icons.account_box)), equals(const Size(35, 35)),);
// When IconTheme is closer to IconButton, IconButtonTheme still overrides IconTheme
await tester.pumpWidget(
MaterialApp(
theme: ThemeData.from(
colorScheme: const ColorScheme.light(),
useMaterial3: true,
),
home: IconTheme.merge(
data: const IconThemeData(color: Colors.blue, size: 35),
child: IconButtonTheme(
data: IconButtonThemeData(style: IconButton.styleFrom(foregroundColor: Colors.orange, iconSize: 36)),
child: IconButton(
icon: const Icon(Icons.account_box),
onPressed: () {},
),
),
)
)
);
Color? iconColor1() => _iconStyle(tester, Icons.account_box)?.color;
expect(iconColor1(), Colors.orange);
expect(tester.getSize(find.byIcon(Icons.account_box)), equals(const Size(36, 36)),);
});
testWidgets('White icon color defined by users shows correctly in Material3', (WidgetTester tester) async {
await tester.pumpWidget(
MaterialApp(
theme: ThemeData.from(
colorScheme: const ColorScheme.dark(),
useMaterial3: true,
).copyWith(
iconTheme: const IconThemeData(color: Colors.white),
),
home: IconButton(
icon: const Icon(Icons.account_box),
onPressed: () {},
)
)
);
Color? iconColor1() => _iconStyle(tester, Icons.account_box)?.color;
expect(iconColor1(), Colors.white);
});
testWidgets('In light mode, icon color is M3 default color instead of IconTheme.of(context).color, '
'if only setting color in IconTheme', (WidgetTester tester) async {
final ColorScheme darkScheme = const ColorScheme.dark().copyWith(onSurfaceVariant: const Color(0xffe91e60));
// Brightness.dark
await tester.pumpWidget(
MaterialApp(
theme: ThemeData(colorScheme: darkScheme, useMaterial3: true,),
home: Scaffold(
body: IconTheme.merge(
data: const IconThemeData(size: 26),
child: IconButton(
icon: const Icon(Icons.account_box),
onPressed: () {},
),
),
)
)
);
Color? iconColor0() => _iconStyle(tester, Icons.account_box)?.color;
expect(iconColor0(), darkScheme.onSurfaceVariant); // onSurfaceVariant
});
testWidgets('In dark mode, icon color is M3 default color instead of IconTheme.of(context).color, '
'if only setting color in IconTheme', (WidgetTester tester) async {
final ColorScheme lightScheme = const ColorScheme.light().copyWith(onSurfaceVariant: const Color(0xffe91e60));
// Brightness.dark
await tester.pumpWidget(
MaterialApp(
theme: ThemeData(colorScheme: lightScheme, useMaterial3: true,),
home: Scaffold(
body: IconTheme.merge(
data: const IconThemeData(size: 26),
child: IconButton(
icon: const Icon(Icons.account_box),
onPressed: () {},
),
),
)
)
);
Color? iconColor0() => _iconStyle(tester, Icons.account_box)?.color;
expect(iconColor0(), lightScheme.onSurfaceVariant); // onSurfaceVariant
});
testWidgets('black87 icon color defined by users shows correctly in Material3', (WidgetTester tester) async {
});
});
}
Widget wrap({required Widget child, required bool useMaterial3}) {
return useMaterial3
? MaterialApp(
theme: ThemeData.from(colorScheme: const ColorScheme.light(), useMaterial3: true),
home: FocusTraversalGroup(
policy: ReadingOrderTraversalPolicy(),
child: Directionality(
textDirection: TextDirection.ltr,
child: Center(child: child),
)),
)
: FocusTraversalGroup(
policy: ReadingOrderTraversalPolicy(),
child: Directionality(
textDirection: TextDirection.ltr,
child: Material(
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;
}