Skip to content

Commit 059189e

Browse files
authored
Adds Missing onHover & onFocusChange for OutlinedButton.icon (#144374)
Adds Missing `onHover` & `onFocusChange` for `OutlinedButton.icon`. I've copy-pasted the tests of OutlinedButton for OutlinedButton.icon. Also, `onHover` & `onFocusChange` are already there in ElevatedButton.icon, FilledButton.icon & TextButton.icon, but tests files for these 3 doesn't test for icon variants. Fixes #144256
1 parent 1635e64 commit 059189e

File tree

2 files changed

+175
-0
lines changed

2 files changed

+175
-0
lines changed

packages/flutter/lib/src/material/outlined_button.dart

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,8 @@ class OutlinedButton extends ButtonStyleButton {
9595
Key? key,
9696
required VoidCallback? onPressed,
9797
VoidCallback? onLongPress,
98+
ValueChanged<bool>? onHover,
99+
ValueChanged<bool>? onFocusChange,
98100
ButtonStyle? style,
99101
FocusNode? focusNode,
100102
bool? autofocus,
@@ -109,6 +111,8 @@ class OutlinedButton extends ButtonStyleButton {
109111
key: key,
110112
onPressed: onPressed,
111113
onLongPress: onLongPress,
114+
onHover: onHover,
115+
onFocusChange: onFocusChange,
112116
style: style,
113117
focusNode: focusNode,
114118
autofocus: autofocus ?? false,
@@ -121,6 +125,8 @@ class OutlinedButton extends ButtonStyleButton {
121125
key: key,
122126
onPressed: onPressed,
123127
onLongPress: onLongPress,
128+
onHover: onHover,
129+
onFocusChange: onFocusChange,
124130
style: style,
125131
focusNode: focusNode,
126132
autofocus: autofocus ?? false,
@@ -454,6 +460,8 @@ class _OutlinedButtonWithIcon extends OutlinedButton {
454460
super.key,
455461
required super.onPressed,
456462
super.onLongPress,
463+
super.onHover,
464+
super.onFocusChange,
457465
super.style,
458466
super.focusNode,
459467
bool? autofocus,

packages/flutter/test/material/outlined_button_test.dart

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2467,6 +2467,173 @@ void main() {
24672467
// The icon is aligned to the left of the button.
24682468
expect(buttonTopLeft.dx, iconTopLeft.dx - 24.0); // 24.0 - padding between icon and button edge.
24692469
});
2470+
2471+
testWidgets("OutlinedButton.icon response doesn't hover when disabled", (WidgetTester tester) async {
2472+
FocusManager.instance.highlightStrategy = FocusHighlightStrategy.alwaysTouch;
2473+
final FocusNode focusNode = FocusNode(debugLabel: 'OutlinedButton.icon Focus');
2474+
final GlobalKey childKey = GlobalKey();
2475+
bool hovering = false;
2476+
await tester.pumpWidget(
2477+
Directionality(
2478+
textDirection: TextDirection.ltr,
2479+
child: SizedBox(
2480+
width: 100,
2481+
height: 100,
2482+
child: OutlinedButton.icon(
2483+
autofocus: true,
2484+
onPressed: () {},
2485+
onLongPress: () {},
2486+
onHover: (bool value) { hovering = value; },
2487+
focusNode: focusNode,
2488+
label: SizedBox(key: childKey),
2489+
icon: const Icon(Icons.add),
2490+
),
2491+
),
2492+
),
2493+
);
2494+
await tester.pumpAndSettle();
2495+
expect(focusNode.hasPrimaryFocus, isTrue);
2496+
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
2497+
await gesture.addPointer();
2498+
await gesture.moveTo(tester.getCenter(find.byKey(childKey)));
2499+
await tester.pumpAndSettle();
2500+
expect(hovering, isTrue);
2501+
2502+
await tester.pumpWidget(
2503+
Directionality(
2504+
textDirection: TextDirection.ltr,
2505+
child: SizedBox(
2506+
width: 100,
2507+
height: 100,
2508+
child: OutlinedButton.icon(
2509+
focusNode: focusNode,
2510+
onHover: (bool value) { hovering = value; },
2511+
onPressed: null,
2512+
label: SizedBox(key: childKey),
2513+
icon: const Icon(Icons.add),
2514+
),
2515+
),
2516+
),
2517+
);
2518+
2519+
await tester.pumpAndSettle();
2520+
expect(focusNode.hasPrimaryFocus, isFalse);
2521+
focusNode.dispose();
2522+
});
2523+
2524+
testWidgets('disabled and hovered OutlinedButton.icon responds to mouse-exit', (WidgetTester tester) async {
2525+
int onHoverCount = 0;
2526+
late bool hover;
2527+
const Key key = Key('OutlinedButton.icon');
2528+
Widget buildFrame({ required bool enabled }) {
2529+
return Directionality(
2530+
textDirection: TextDirection.ltr,
2531+
child: Center(
2532+
child: SizedBox(
2533+
width: 100,
2534+
height: 100,
2535+
child: OutlinedButton.icon(
2536+
key: key,
2537+
onPressed: enabled ? () { } : null,
2538+
onHover: (bool value) {
2539+
onHoverCount += 1;
2540+
hover = value;
2541+
},
2542+
label: const Text('OutlinedButton'),
2543+
icon: const Icon(Icons.add),
2544+
),
2545+
),
2546+
),
2547+
);
2548+
}
2549+
2550+
await tester.pumpWidget(buildFrame(enabled: true));
2551+
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
2552+
await gesture.addPointer();
2553+
2554+
await gesture.moveTo(tester.getCenter(find.byKey(key)));
2555+
await tester.pumpAndSettle();
2556+
expect(onHoverCount, 1);
2557+
expect(hover, true);
2558+
2559+
await tester.pumpWidget(buildFrame(enabled: false));
2560+
await tester.pumpAndSettle();
2561+
await gesture.moveTo(Offset.zero);
2562+
// Even though the OutlinedButton has been disabled, the mouse-exit still
2563+
// causes onHover(false) to be called.
2564+
expect(onHoverCount, 2);
2565+
expect(hover, false);
2566+
2567+
await gesture.moveTo(tester.getCenter(find.byKey(key)));
2568+
await tester.pumpAndSettle();
2569+
// We no longer see hover events because the OutlinedButton is disabled
2570+
// and it's no longer in the "hovering" state.
2571+
expect(onHoverCount, 2);
2572+
expect(hover, false);
2573+
2574+
await tester.pumpWidget(buildFrame(enabled: true));
2575+
await tester.pumpAndSettle();
2576+
// The OutlinedButton was enabled while it contained the mouse, however
2577+
// we do not call onHover() because it may call setState().
2578+
expect(onHoverCount, 2);
2579+
expect(hover, false);
2580+
2581+
await gesture.moveTo(tester.getCenter(find.byKey(key)) - const Offset(1, 1));
2582+
await tester.pumpAndSettle();
2583+
// Moving the mouse a little within the OutlinedButton doesn't change anything.
2584+
expect(onHoverCount, 2);
2585+
expect(hover, false);
2586+
});
2587+
2588+
testWidgets('Can set OutlinedButton.icon focus and Can set unFocus.', (WidgetTester tester) async {
2589+
final FocusNode node = FocusNode(debugLabel: 'OutlinedButton.icon Focus');
2590+
bool gotFocus = false;
2591+
await tester.pumpWidget(
2592+
Directionality(
2593+
textDirection: TextDirection.ltr,
2594+
child: OutlinedButton.icon(
2595+
focusNode: node,
2596+
onFocusChange: (bool focused) => gotFocus = focused,
2597+
onPressed: () { },
2598+
label: const SizedBox(),
2599+
icon: const Icon(Icons.add),
2600+
),
2601+
),
2602+
);
2603+
2604+
node.requestFocus();
2605+
await tester.pump();
2606+
expect(gotFocus, isTrue);
2607+
expect(node.hasFocus, isTrue);
2608+
node.unfocus();
2609+
await tester.pump();
2610+
expect(gotFocus, isFalse);
2611+
expect(node.hasFocus, isFalse);
2612+
node.dispose();
2613+
});
2614+
2615+
testWidgets('When OutlinedButton.icon disable, Can not set OutlinedButton.icon focus.', (WidgetTester tester) async {
2616+
final FocusNode node = FocusNode(debugLabel: 'OutlinedButton.icon Focus');
2617+
bool gotFocus = false;
2618+
await tester.pumpWidget(
2619+
Directionality(
2620+
textDirection: TextDirection.ltr,
2621+
child: OutlinedButton.icon(
2622+
focusNode: node,
2623+
onFocusChange: (bool focused) => gotFocus = focused,
2624+
onPressed: null,
2625+
label: const SizedBox(),
2626+
icon: const Icon(Icons.add),
2627+
),
2628+
),
2629+
);
2630+
2631+
node.requestFocus();
2632+
await tester.pump();
2633+
expect(gotFocus, isFalse);
2634+
expect(node.hasFocus, isFalse);
2635+
node.dispose();
2636+
});
24702637
}
24712638

24722639
TextStyle _iconStyle(WidgetTester tester, IconData icon) {

0 commit comments

Comments
 (0)