make Elevated&Outlined&TextButton support onHover&onFocus callback (#90688)
This commit is contained in:
1
AUTHORS
1
AUTHORS
@@ -85,3 +85,4 @@ Callum Moffat <callum@moffatman.com>
|
||||
Koutaro Mori <koutaro.mo@gmail.com>
|
||||
Sergei Smitskoi <sergflutterdev@gmail.com>
|
||||
Pradumna Saraf <pradumnasaraf@gmail.com>
|
||||
Kai Yu <yk3372@gmail.com>
|
||||
|
||||
@@ -33,6 +33,8 @@ abstract class ButtonStyleButton extends StatefulWidget {
|
||||
Key? key,
|
||||
required this.onPressed,
|
||||
required this.onLongPress,
|
||||
required this.onHover,
|
||||
required this.onFocusChange,
|
||||
required this.style,
|
||||
required this.focusNode,
|
||||
required this.autofocus,
|
||||
@@ -60,6 +62,19 @@ abstract class ButtonStyleButton extends StatefulWidget {
|
||||
/// * [enabled], which is true if the button is enabled.
|
||||
final VoidCallback? onLongPress;
|
||||
|
||||
/// Called when a pointer enters or exits the button response area.
|
||||
///
|
||||
/// The value passed to the callback is true if a pointer has entered this
|
||||
/// part of the material and false if a pointer has exited this part of the
|
||||
/// material.
|
||||
final ValueChanged<bool>? onHover;
|
||||
|
||||
/// Handler called when the focus changes.
|
||||
///
|
||||
/// Called with true if this widget's node gains focus, and false if it loses
|
||||
/// focus.
|
||||
final ValueChanged<bool>? onFocusChange;
|
||||
|
||||
/// Customizes this button's appearance.
|
||||
///
|
||||
/// Non-null properties of this style override the corresponding
|
||||
@@ -335,12 +350,18 @@ class _ButtonStyleState extends State<ButtonStyleButton> with MaterialStateMixin
|
||||
onTap: widget.onPressed,
|
||||
onLongPress: widget.onLongPress,
|
||||
onHighlightChanged: updateMaterialState(MaterialState.pressed),
|
||||
onHover: updateMaterialState(MaterialState.hovered),
|
||||
onHover: updateMaterialState(
|
||||
MaterialState.hovered,
|
||||
onChanged: widget.onHover,
|
||||
),
|
||||
mouseCursor: resolvedMouseCursor,
|
||||
enableFeedback: resolvedEnableFeedback,
|
||||
focusNode: widget.focusNode,
|
||||
canRequestFocus: widget.enabled,
|
||||
onFocusChange: updateMaterialState(MaterialState.focused),
|
||||
onFocusChange: updateMaterialState(
|
||||
MaterialState.focused,
|
||||
onChanged: widget.onFocusChange,
|
||||
),
|
||||
autofocus: widget.autofocus,
|
||||
splashFactory: resolvedSplashFactory,
|
||||
overlayColor: overlayColor,
|
||||
|
||||
@@ -65,6 +65,8 @@ class ElevatedButton extends ButtonStyleButton {
|
||||
Key? key,
|
||||
required VoidCallback? onPressed,
|
||||
VoidCallback? onLongPress,
|
||||
ValueChanged<bool>? onHover,
|
||||
ValueChanged<bool>? onFocusChange,
|
||||
ButtonStyle? style,
|
||||
FocusNode? focusNode,
|
||||
bool autofocus = false,
|
||||
@@ -74,6 +76,8 @@ class ElevatedButton extends ButtonStyleButton {
|
||||
key: key,
|
||||
onPressed: onPressed,
|
||||
onLongPress: onLongPress,
|
||||
onHover: onHover,
|
||||
onFocusChange: onFocusChange,
|
||||
style: style,
|
||||
focusNode: focusNode,
|
||||
autofocus: autofocus,
|
||||
@@ -92,6 +96,8 @@ class ElevatedButton extends ButtonStyleButton {
|
||||
Key? key,
|
||||
required VoidCallback? onPressed,
|
||||
VoidCallback? onLongPress,
|
||||
ValueChanged<bool>? onHover,
|
||||
ValueChanged<bool>? onFocusChange,
|
||||
ButtonStyle? style,
|
||||
FocusNode? focusNode,
|
||||
bool? autofocus,
|
||||
@@ -399,6 +405,8 @@ class _ElevatedButtonWithIcon extends ElevatedButton {
|
||||
Key? key,
|
||||
required VoidCallback? onPressed,
|
||||
VoidCallback? onLongPress,
|
||||
ValueChanged<bool>? onHover,
|
||||
ValueChanged<bool>? onFocusChange,
|
||||
ButtonStyle? style,
|
||||
FocusNode? focusNode,
|
||||
bool? autofocus,
|
||||
@@ -411,6 +419,8 @@ class _ElevatedButtonWithIcon extends ElevatedButton {
|
||||
key: key,
|
||||
onPressed: onPressed,
|
||||
onLongPress: onLongPress,
|
||||
onHover: onHover,
|
||||
onFocusChange: onFocusChange,
|
||||
style: style,
|
||||
focusNode: focusNode,
|
||||
autofocus: autofocus ?? false,
|
||||
|
||||
@@ -70,6 +70,8 @@ class OutlinedButton extends ButtonStyleButton {
|
||||
Key? key,
|
||||
required VoidCallback? onPressed,
|
||||
VoidCallback? onLongPress,
|
||||
ValueChanged<bool>? onHover,
|
||||
ValueChanged<bool>? onFocusChange,
|
||||
ButtonStyle? style,
|
||||
FocusNode? focusNode,
|
||||
bool autofocus = false,
|
||||
@@ -79,6 +81,8 @@ class OutlinedButton extends ButtonStyleButton {
|
||||
key: key,
|
||||
onPressed: onPressed,
|
||||
onLongPress: onLongPress,
|
||||
onHover: onHover,
|
||||
onFocusChange: onFocusChange,
|
||||
style: style,
|
||||
focusNode: focusNode,
|
||||
autofocus: autofocus,
|
||||
|
||||
@@ -70,6 +70,8 @@ class TextButton extends ButtonStyleButton {
|
||||
Key? key,
|
||||
required VoidCallback? onPressed,
|
||||
VoidCallback? onLongPress,
|
||||
ValueChanged<bool>? onHover,
|
||||
ValueChanged<bool>? onFocusChange,
|
||||
ButtonStyle? style,
|
||||
FocusNode? focusNode,
|
||||
bool autofocus = false,
|
||||
@@ -79,6 +81,8 @@ class TextButton extends ButtonStyleButton {
|
||||
key: key,
|
||||
onPressed: onPressed,
|
||||
onLongPress: onLongPress,
|
||||
onHover: onHover,
|
||||
onFocusChange: onFocusChange,
|
||||
style: style,
|
||||
focusNode: focusNode,
|
||||
autofocus: autofocus,
|
||||
@@ -97,6 +101,8 @@ class TextButton extends ButtonStyleButton {
|
||||
Key? key,
|
||||
required VoidCallback? onPressed,
|
||||
VoidCallback? onLongPress,
|
||||
ValueChanged<bool>? onHover,
|
||||
ValueChanged<bool>? onFocusChange,
|
||||
ButtonStyle? style,
|
||||
FocusNode? focusNode,
|
||||
bool? autofocus,
|
||||
@@ -362,6 +368,8 @@ class _TextButtonWithIcon extends TextButton {
|
||||
Key? key,
|
||||
required VoidCallback? onPressed,
|
||||
VoidCallback? onLongPress,
|
||||
ValueChanged<bool>? onHover,
|
||||
ValueChanged<bool>? onFocusChange,
|
||||
ButtonStyle? style,
|
||||
FocusNode? focusNode,
|
||||
bool? autofocus,
|
||||
@@ -374,6 +382,8 @@ class _TextButtonWithIcon extends TextButton {
|
||||
key: key,
|
||||
onPressed: onPressed,
|
||||
onLongPress: onLongPress,
|
||||
onHover: onHover,
|
||||
onFocusChange: onFocusChange,
|
||||
style: style,
|
||||
focusNode: focusNode,
|
||||
autofocus: autofocus ?? false,
|
||||
|
||||
@@ -414,6 +414,182 @@ void main() {
|
||||
expect(didLongPressButton, isTrue);
|
||||
});
|
||||
|
||||
testWidgets("ElevatedButton response doesn't hover when disabled", (WidgetTester tester) async {
|
||||
FocusManager.instance.highlightStrategy = FocusHighlightStrategy.alwaysTouch;
|
||||
final FocusNode focusNode = FocusNode(debugLabel: 'ElevatedButton Focus');
|
||||
final GlobalKey childKey = GlobalKey();
|
||||
bool hovering = false;
|
||||
await tester.pumpWidget(
|
||||
Material(
|
||||
child: Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: SizedBox(
|
||||
width: 100,
|
||||
height: 100,
|
||||
child: ElevatedButton(
|
||||
autofocus: true,
|
||||
onPressed: () {},
|
||||
onLongPress: () {},
|
||||
onHover: (bool value) { hovering = value; },
|
||||
focusNode: focusNode,
|
||||
child: SizedBox(key: childKey),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
expect(focusNode.hasPrimaryFocus, isTrue);
|
||||
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
|
||||
await gesture.addPointer();
|
||||
addTearDown(gesture.removePointer);
|
||||
await gesture.moveTo(tester.getCenter(find.byKey(childKey)));
|
||||
await tester.pumpAndSettle();
|
||||
expect(hovering, isTrue);
|
||||
|
||||
await tester.pumpWidget(
|
||||
Material(
|
||||
child: Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: SizedBox(
|
||||
width: 100,
|
||||
height: 100,
|
||||
child: ElevatedButton(
|
||||
focusNode: focusNode,
|
||||
onHover: (bool value) { hovering = value; },
|
||||
onPressed: null,
|
||||
child: SizedBox(key: childKey),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
expect(focusNode.hasPrimaryFocus, isFalse);
|
||||
});
|
||||
|
||||
testWidgets('disabled and hovered ElevatedButton responds to mouse-exit', (WidgetTester tester) async {
|
||||
int onHoverCount = 0;
|
||||
late bool hover;
|
||||
|
||||
Widget buildFrame({ required bool enabled }) {
|
||||
return Material(
|
||||
child: Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: Center(
|
||||
child: SizedBox(
|
||||
width: 100,
|
||||
height: 100,
|
||||
child: ElevatedButton(
|
||||
onPressed: enabled ? () { } : null,
|
||||
onHover: (bool value) {
|
||||
onHoverCount += 1;
|
||||
hover = value;
|
||||
},
|
||||
child: const Text('ElevatedButton'),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
await tester.pumpWidget(buildFrame(enabled: true));
|
||||
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
|
||||
await gesture.addPointer();
|
||||
addTearDown(gesture.removePointer);
|
||||
|
||||
await gesture.moveTo(tester.getCenter(find.byType(ElevatedButton)));
|
||||
await tester.pumpAndSettle();
|
||||
expect(onHoverCount, 1);
|
||||
expect(hover, true);
|
||||
|
||||
await tester.pumpWidget(buildFrame(enabled: false));
|
||||
await tester.pumpAndSettle();
|
||||
await gesture.moveTo(Offset.zero);
|
||||
// Even though the ElevatedButton has been disabled, the mouse-exit still
|
||||
// causes onHover(false) to be called.
|
||||
expect(onHoverCount, 2);
|
||||
expect(hover, false);
|
||||
|
||||
await gesture.moveTo(tester.getCenter(find.byType(ElevatedButton)));
|
||||
await tester.pumpAndSettle();
|
||||
// We no longer see hover events because the ElevatedButton is disabled
|
||||
// and it's no longer in the "hovering" state.
|
||||
expect(onHoverCount, 2);
|
||||
expect(hover, false);
|
||||
|
||||
await tester.pumpWidget(buildFrame(enabled: true));
|
||||
await tester.pumpAndSettle();
|
||||
// The ElevatedButton was enabled while it contained the mouse, however
|
||||
// we do not call onHover() because it may call setState().
|
||||
expect(onHoverCount, 2);
|
||||
expect(hover, false);
|
||||
|
||||
await gesture.moveTo(tester.getCenter(find.byType(ElevatedButton)) - const Offset(1, 1));
|
||||
await tester.pumpAndSettle();
|
||||
// Moving the mouse a little within the ElevatedButton doesn't change anything.
|
||||
expect(onHoverCount, 2);
|
||||
expect(hover, false);
|
||||
});
|
||||
|
||||
testWidgets('Can set ElevatedButton focus and Can set unFocus.', (WidgetTester tester) async {
|
||||
final FocusNode node = FocusNode(debugLabel: 'ElevatedButton Focus');
|
||||
bool gotFocus = false;
|
||||
await tester.pumpWidget(
|
||||
Material(
|
||||
child: Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: ElevatedButton(
|
||||
focusNode: node,
|
||||
onFocusChange: (bool focused) => gotFocus = focused,
|
||||
onPressed: () { },
|
||||
child: const SizedBox(),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
node.requestFocus();
|
||||
|
||||
await tester.pump();
|
||||
|
||||
expect(gotFocus, isTrue);
|
||||
expect(node.hasFocus, isTrue);
|
||||
|
||||
node.unfocus();
|
||||
await tester.pump();
|
||||
|
||||
expect(gotFocus, isFalse);
|
||||
expect(node.hasFocus, isFalse);
|
||||
});
|
||||
|
||||
testWidgets('When ElevatedButton disable, Can not set ElevatedButton focus.', (WidgetTester tester) async {
|
||||
final FocusNode node = FocusNode(debugLabel: 'ElevatedButton Focus');
|
||||
bool gotFocus = false;
|
||||
await tester.pumpWidget(
|
||||
Material(
|
||||
child: Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: ElevatedButton(
|
||||
focusNode: node,
|
||||
onFocusChange: (bool focused) => gotFocus = focused,
|
||||
onPressed: null,
|
||||
child: const SizedBox(),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
node.requestFocus();
|
||||
|
||||
await tester.pump();
|
||||
|
||||
expect(gotFocus, isFalse);
|
||||
expect(node.hasFocus, isFalse);
|
||||
});
|
||||
|
||||
testWidgets('Does ElevatedButton work with hover', (WidgetTester tester) async {
|
||||
const Color hoverColor = Color(0xff001122);
|
||||
|
||||
|
||||
@@ -613,6 +613,182 @@ void main() {
|
||||
expect(tester.widget<OutlinedButton>(outlinedButton).enabled, false);
|
||||
});
|
||||
|
||||
testWidgets("OutlinedButton response doesn't hover when disabled", (WidgetTester tester) async {
|
||||
FocusManager.instance.highlightStrategy = FocusHighlightStrategy.alwaysTouch;
|
||||
final FocusNode focusNode = FocusNode(debugLabel: 'OutlinedButton Focus');
|
||||
final GlobalKey childKey = GlobalKey();
|
||||
bool hovering = false;
|
||||
await tester.pumpWidget(
|
||||
Material(
|
||||
child: Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: SizedBox(
|
||||
width: 100,
|
||||
height: 100,
|
||||
child: OutlinedButton(
|
||||
autofocus: true,
|
||||
onPressed: () {},
|
||||
onLongPress: () {},
|
||||
onHover: (bool value) { hovering = value; },
|
||||
focusNode: focusNode,
|
||||
child: SizedBox(key: childKey),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
expect(focusNode.hasPrimaryFocus, isTrue);
|
||||
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
|
||||
await gesture.addPointer();
|
||||
addTearDown(gesture.removePointer);
|
||||
await gesture.moveTo(tester.getCenter(find.byKey(childKey)));
|
||||
await tester.pumpAndSettle();
|
||||
expect(hovering, isTrue);
|
||||
|
||||
await tester.pumpWidget(
|
||||
Material(
|
||||
child: Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: SizedBox(
|
||||
width: 100,
|
||||
height: 100,
|
||||
child: OutlinedButton(
|
||||
focusNode: focusNode,
|
||||
onHover: (bool value) { hovering = value; },
|
||||
onPressed: null,
|
||||
child: SizedBox(key: childKey),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
expect(focusNode.hasPrimaryFocus, isFalse);
|
||||
});
|
||||
|
||||
testWidgets('disabled and hovered OutlinedButton responds to mouse-exit', (WidgetTester tester) async {
|
||||
int onHoverCount = 0;
|
||||
late bool hover;
|
||||
|
||||
Widget buildFrame({ required bool enabled }) {
|
||||
return Material(
|
||||
child: Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: Center(
|
||||
child: SizedBox(
|
||||
width: 100,
|
||||
height: 100,
|
||||
child: OutlinedButton(
|
||||
onPressed: enabled ? () { } : null,
|
||||
onHover: (bool value) {
|
||||
onHoverCount += 1;
|
||||
hover = value;
|
||||
},
|
||||
child: const Text('OutlinedButton'),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
await tester.pumpWidget(buildFrame(enabled: true));
|
||||
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
|
||||
await gesture.addPointer();
|
||||
addTearDown(gesture.removePointer);
|
||||
|
||||
await gesture.moveTo(tester.getCenter(find.byType(OutlinedButton)));
|
||||
await tester.pumpAndSettle();
|
||||
expect(onHoverCount, 1);
|
||||
expect(hover, true);
|
||||
|
||||
await tester.pumpWidget(buildFrame(enabled: false));
|
||||
await tester.pumpAndSettle();
|
||||
await gesture.moveTo(Offset.zero);
|
||||
// Even though the OutlinedButton has been disabled, the mouse-exit still
|
||||
// causes onHover(false) to be called.
|
||||
expect(onHoverCount, 2);
|
||||
expect(hover, false);
|
||||
|
||||
await gesture.moveTo(tester.getCenter(find.byType(OutlinedButton)));
|
||||
await tester.pumpAndSettle();
|
||||
// We no longer see hover events because the OutlinedButton is disabled
|
||||
// and it's no longer in the "hovering" state.
|
||||
expect(onHoverCount, 2);
|
||||
expect(hover, false);
|
||||
|
||||
await tester.pumpWidget(buildFrame(enabled: true));
|
||||
await tester.pumpAndSettle();
|
||||
// The OutlinedButton was enabled while it contained the mouse, however
|
||||
// we do not call onHover() because it may call setState().
|
||||
expect(onHoverCount, 2);
|
||||
expect(hover, false);
|
||||
|
||||
await gesture.moveTo(tester.getCenter(find.byType(OutlinedButton)) - const Offset(1, 1));
|
||||
await tester.pumpAndSettle();
|
||||
// Moving the mouse a little within the OutlinedButton doesn't change anything.
|
||||
expect(onHoverCount, 2);
|
||||
expect(hover, false);
|
||||
});
|
||||
|
||||
testWidgets('Can set OutlinedButton focus and Can set unFocus.', (WidgetTester tester) async {
|
||||
final FocusNode node = FocusNode(debugLabel: 'OutlinedButton Focus');
|
||||
bool gotFocus = false;
|
||||
await tester.pumpWidget(
|
||||
Material(
|
||||
child: Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: OutlinedButton(
|
||||
focusNode: node,
|
||||
onFocusChange: (bool focused) => gotFocus = focused,
|
||||
onPressed: () { },
|
||||
child: const SizedBox(),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
node.requestFocus();
|
||||
|
||||
await tester.pump();
|
||||
|
||||
expect(gotFocus, isTrue);
|
||||
expect(node.hasFocus, isTrue);
|
||||
|
||||
node.unfocus();
|
||||
await tester.pump();
|
||||
|
||||
expect(gotFocus, isFalse);
|
||||
expect(node.hasFocus, isFalse);
|
||||
});
|
||||
|
||||
testWidgets('When OutlinedButton disable, Can not set OutlinedButton focus.', (WidgetTester tester) async {
|
||||
final FocusNode node = FocusNode(debugLabel: 'OutlinedButton Focus');
|
||||
bool gotFocus = false;
|
||||
await tester.pumpWidget(
|
||||
Material(
|
||||
child: Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: OutlinedButton(
|
||||
focusNode: node,
|
||||
onFocusChange: (bool focused) => gotFocus = focused,
|
||||
onPressed: null,
|
||||
child: const SizedBox(),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
node.requestFocus();
|
||||
|
||||
await tester.pump();
|
||||
|
||||
expect(gotFocus, isFalse);
|
||||
expect(node.hasFocus, isFalse);
|
||||
});
|
||||
|
||||
testWidgets("Outline button doesn't crash if disabled during a gesture", (WidgetTester tester) async {
|
||||
Widget buildFrame(VoidCallback? onPressed) {
|
||||
return Directionality(
|
||||
|
||||
@@ -697,6 +697,182 @@ void main() {
|
||||
expect(didLongPressButton, isTrue);
|
||||
});
|
||||
|
||||
testWidgets("TextButton response doesn't hover when disabled", (WidgetTester tester) async {
|
||||
FocusManager.instance.highlightStrategy = FocusHighlightStrategy.alwaysTouch;
|
||||
final FocusNode focusNode = FocusNode(debugLabel: 'TextButton Focus');
|
||||
final GlobalKey childKey = GlobalKey();
|
||||
bool hovering = false;
|
||||
await tester.pumpWidget(
|
||||
Material(
|
||||
child: Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: SizedBox(
|
||||
width: 100,
|
||||
height: 100,
|
||||
child: TextButton(
|
||||
autofocus: true,
|
||||
onPressed: () {},
|
||||
onLongPress: () {},
|
||||
onHover: (bool value) { hovering = value; },
|
||||
focusNode: focusNode,
|
||||
child: SizedBox(key: childKey),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
expect(focusNode.hasPrimaryFocus, isTrue);
|
||||
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
|
||||
await gesture.addPointer();
|
||||
addTearDown(gesture.removePointer);
|
||||
await gesture.moveTo(tester.getCenter(find.byKey(childKey)));
|
||||
await tester.pumpAndSettle();
|
||||
expect(hovering, isTrue);
|
||||
|
||||
await tester.pumpWidget(
|
||||
Material(
|
||||
child: Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: SizedBox(
|
||||
width: 100,
|
||||
height: 100,
|
||||
child: TextButton(
|
||||
focusNode: focusNode,
|
||||
onHover: (bool value) { hovering = value; },
|
||||
onPressed: null,
|
||||
child: SizedBox(key: childKey),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
expect(focusNode.hasPrimaryFocus, isFalse);
|
||||
});
|
||||
|
||||
testWidgets('disabled and hovered TextButton responds to mouse-exit', (WidgetTester tester) async {
|
||||
int onHoverCount = 0;
|
||||
late bool hover;
|
||||
|
||||
Widget buildFrame({ required bool enabled }) {
|
||||
return Material(
|
||||
child: Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: Center(
|
||||
child: SizedBox(
|
||||
width: 100,
|
||||
height: 100,
|
||||
child: TextButton(
|
||||
onPressed: enabled ? () { } : null,
|
||||
onHover: (bool value) {
|
||||
onHoverCount += 1;
|
||||
hover = value;
|
||||
},
|
||||
child: const Text('TextButton'),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
await tester.pumpWidget(buildFrame(enabled: true));
|
||||
final TestGesture gesture = await tester.createGesture(kind: PointerDeviceKind.mouse);
|
||||
await gesture.addPointer();
|
||||
addTearDown(gesture.removePointer);
|
||||
|
||||
await gesture.moveTo(tester.getCenter(find.byType(TextButton)));
|
||||
await tester.pumpAndSettle();
|
||||
expect(onHoverCount, 1);
|
||||
expect(hover, true);
|
||||
|
||||
await tester.pumpWidget(buildFrame(enabled: false));
|
||||
await tester.pumpAndSettle();
|
||||
await gesture.moveTo(Offset.zero);
|
||||
// Even though the TextButton has been disabled, the mouse-exit still
|
||||
// causes onHover(false) to be called.
|
||||
expect(onHoverCount, 2);
|
||||
expect(hover, false);
|
||||
|
||||
await gesture.moveTo(tester.getCenter(find.byType(TextButton)));
|
||||
await tester.pumpAndSettle();
|
||||
// We no longer see hover events because the TextButton is disabled
|
||||
// and it's no longer in the "hovering" state.
|
||||
expect(onHoverCount, 2);
|
||||
expect(hover, false);
|
||||
|
||||
await tester.pumpWidget(buildFrame(enabled: true));
|
||||
await tester.pumpAndSettle();
|
||||
// The TextButton was enabled while it contained the mouse, however
|
||||
// we do not call onHover() because it may call setState().
|
||||
expect(onHoverCount, 2);
|
||||
expect(hover, false);
|
||||
|
||||
await gesture.moveTo(tester.getCenter(find.byType(TextButton)) - const Offset(1, 1));
|
||||
await tester.pumpAndSettle();
|
||||
// Moving the mouse a little within the TextButton doesn't change anything.
|
||||
expect(onHoverCount, 2);
|
||||
expect(hover, false);
|
||||
});
|
||||
|
||||
testWidgets('Can set TextButton focus and Can set unFocus.', (WidgetTester tester) async {
|
||||
final FocusNode node = FocusNode(debugLabel: 'TextButton Focus');
|
||||
bool gotFocus = false;
|
||||
await tester.pumpWidget(
|
||||
Material(
|
||||
child: Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: TextButton(
|
||||
focusNode: node,
|
||||
onFocusChange: (bool focused) => gotFocus = focused,
|
||||
onPressed: () { },
|
||||
child: const SizedBox(),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
node.requestFocus();
|
||||
|
||||
await tester.pump();
|
||||
|
||||
expect(gotFocus, isTrue);
|
||||
expect(node.hasFocus, isTrue);
|
||||
|
||||
node.unfocus();
|
||||
await tester.pump();
|
||||
|
||||
expect(gotFocus, isFalse);
|
||||
expect(node.hasFocus, isFalse);
|
||||
});
|
||||
|
||||
testWidgets('When TextButton disable, Can not set TextButton focus.', (WidgetTester tester) async {
|
||||
final FocusNode node = FocusNode(debugLabel: 'TextButton Focus');
|
||||
bool gotFocus = false;
|
||||
await tester.pumpWidget(
|
||||
Material(
|
||||
child: Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: TextButton(
|
||||
focusNode: node,
|
||||
onFocusChange: (bool focused) => gotFocus = focused,
|
||||
onPressed: null,
|
||||
child: const SizedBox(),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
node.requestFocus();
|
||||
|
||||
await tester.pump();
|
||||
|
||||
expect(gotFocus, isFalse);
|
||||
expect(node.hasFocus, isFalse);
|
||||
});
|
||||
|
||||
testWidgets('TextButton responds to density changes.', (WidgetTester tester) async {
|
||||
const Key key = Key('test');
|
||||
const Key childKey = Key('test child');
|
||||
|
||||
Reference in New Issue
Block a user