Unverified Commit 94b9fa41 authored by Taha Tesser's avatar Taha Tesser Committed by GitHub

Provide an option to update `Focus's semantics under `FocusableActionDetector` (#115833)

Update test

Update comments
parent b9caef58
...@@ -1077,6 +1077,7 @@ class FocusableActionDetector extends StatefulWidget { ...@@ -1077,6 +1077,7 @@ class FocusableActionDetector extends StatefulWidget {
this.onShowHoverHighlight, this.onShowHoverHighlight,
this.onFocusChange, this.onFocusChange,
this.mouseCursor = MouseCursor.defer, this.mouseCursor = MouseCursor.defer,
this.includeFocusSemantics = true,
required this.child, required this.child,
}) : assert(enabled != null), }) : assert(enabled != null),
assert(autofocus != null), assert(autofocus != null),
...@@ -1133,6 +1134,11 @@ class FocusableActionDetector extends StatefulWidget { ...@@ -1133,6 +1134,11 @@ class FocusableActionDetector extends StatefulWidget {
/// cursor to the next region behind it in hit-test order. /// cursor to the next region behind it in hit-test order.
final MouseCursor mouseCursor; final MouseCursor mouseCursor;
/// Whether to include semantics from [Focus].
///
/// Defaults to true.
final bool includeFocusSemantics;
/// The child widget for this [FocusableActionDetector] widget. /// The child widget for this [FocusableActionDetector] widget.
/// ///
/// {@macro flutter.widgets.ProxyWidget.child} /// {@macro flutter.widgets.ProxyWidget.child}
...@@ -1293,6 +1299,7 @@ class _FocusableActionDetectorState extends State<FocusableActionDetector> { ...@@ -1293,6 +1299,7 @@ class _FocusableActionDetectorState extends State<FocusableActionDetector> {
descendantsAreTraversable: widget.descendantsAreTraversable, descendantsAreTraversable: widget.descendantsAreTraversable,
canRequestFocus: _canRequestFocus, canRequestFocus: _canRequestFocus,
onFocusChange: _handleFocusChange, onFocusChange: _handleFocusChange,
includeSemantics: widget.includeFocusSemantics,
child: widget.child, child: widget.child,
), ),
); );
......
...@@ -28,6 +28,7 @@ void main() { ...@@ -28,6 +28,7 @@ void main() {
expect(invoked, isTrue); expect(invoked, isTrue);
}); });
}); });
group(Actions, () { group(Actions, () {
Intent? invokedIntent; Intent? invokedIntent;
Action<Intent>? invokedAction; Action<Intent>? invokedAction;
...@@ -99,6 +100,7 @@ void main() { ...@@ -99,6 +100,7 @@ void main() {
expect(result, isTrue); expect(result, isTrue);
expect(invoked, isTrue); expect(invoked, isTrue);
}); });
testWidgets('maybeInvoke returns null when no action is found', (WidgetTester tester) async { testWidgets('maybeInvoke returns null when no action is found', (WidgetTester tester) async {
final GlobalKey containerKey = GlobalKey(); final GlobalKey containerKey = GlobalKey();
bool invoked = false; bool invoked = false;
...@@ -125,6 +127,7 @@ void main() { ...@@ -125,6 +127,7 @@ void main() {
expect(result, isNull); expect(result, isNull);
expect(invoked, isFalse); expect(invoked, isFalse);
}); });
testWidgets('invoke throws when no action is found', (WidgetTester tester) async { testWidgets('invoke throws when no action is found', (WidgetTester tester) async {
final GlobalKey containerKey = GlobalKey(); final GlobalKey containerKey = GlobalKey();
bool invoked = false; bool invoked = false;
...@@ -151,6 +154,7 @@ void main() { ...@@ -151,6 +154,7 @@ void main() {
expect(result, isNull); expect(result, isNull);
expect(invoked, isFalse); expect(invoked, isFalse);
}); });
testWidgets('Actions widget can invoke actions with custom dispatcher', (WidgetTester tester) async { testWidgets('Actions widget can invoke actions with custom dispatcher', (WidgetTester tester) async {
final GlobalKey containerKey = GlobalKey(); final GlobalKey containerKey = GlobalKey();
bool invoked = false; bool invoked = false;
...@@ -181,6 +185,7 @@ void main() { ...@@ -181,6 +185,7 @@ void main() {
expect(invoked, isTrue); expect(invoked, isTrue);
expect(invokedIntent, equals(intent)); expect(invokedIntent, equals(intent));
}); });
testWidgets('Actions can invoke actions in ancestor dispatcher', (WidgetTester tester) async { testWidgets('Actions can invoke actions in ancestor dispatcher', (WidgetTester tester) async {
final GlobalKey containerKey = GlobalKey(); final GlobalKey containerKey = GlobalKey();
bool invoked = false; bool invoked = false;
...@@ -217,6 +222,7 @@ void main() { ...@@ -217,6 +222,7 @@ void main() {
expect(invokedAction, equals(testAction)); expect(invokedAction, equals(testAction));
expect(invokedDispatcher.runtimeType, equals(TestDispatcher1)); expect(invokedDispatcher.runtimeType, equals(TestDispatcher1));
}); });
testWidgets("Actions can invoke actions in ancestor dispatcher if a lower one isn't specified", (WidgetTester tester) async { testWidgets("Actions can invoke actions in ancestor dispatcher if a lower one isn't specified", (WidgetTester tester) async {
final GlobalKey containerKey = GlobalKey(); final GlobalKey containerKey = GlobalKey();
bool invoked = false; bool invoked = false;
...@@ -252,6 +258,7 @@ void main() { ...@@ -252,6 +258,7 @@ void main() {
expect(invokedAction, equals(testAction)); expect(invokedAction, equals(testAction));
expect(invokedDispatcher.runtimeType, equals(TestDispatcher1)); expect(invokedDispatcher.runtimeType, equals(TestDispatcher1));
}); });
testWidgets('Actions widget can be found with of', (WidgetTester tester) async { testWidgets('Actions widget can be found with of', (WidgetTester tester) async {
final GlobalKey containerKey = GlobalKey(); final GlobalKey containerKey = GlobalKey();
final ActionDispatcher testDispatcher = TestDispatcher1(postInvoke: collect); final ActionDispatcher testDispatcher = TestDispatcher1(postInvoke: collect);
...@@ -268,6 +275,7 @@ void main() { ...@@ -268,6 +275,7 @@ void main() {
final ActionDispatcher dispatcher = Actions.of(containerKey.currentContext!); final ActionDispatcher dispatcher = Actions.of(containerKey.currentContext!);
expect(dispatcher, equals(testDispatcher)); expect(dispatcher, equals(testDispatcher));
}); });
testWidgets('Action can be found with find', (WidgetTester tester) async { testWidgets('Action can be found with find', (WidgetTester tester) async {
final GlobalKey containerKey = GlobalKey(); final GlobalKey containerKey = GlobalKey();
final ActionDispatcher testDispatcher = TestDispatcher1(postInvoke: collect); final ActionDispatcher testDispatcher = TestDispatcher1(postInvoke: collect);
...@@ -314,6 +322,7 @@ void main() { ...@@ -314,6 +322,7 @@ void main() {
expect(() => Actions.find<DoNothingIntent>(containerKey.currentContext!), throwsAssertionError); expect(() => Actions.find<DoNothingIntent>(containerKey.currentContext!), throwsAssertionError);
expect(Actions.maybeFind<DoNothingIntent>(containerKey.currentContext!), isNull); expect(Actions.maybeFind<DoNothingIntent>(containerKey.currentContext!), isNull);
}); });
testWidgets('FocusableActionDetector keeps track of focus and hover even when disabled.', (WidgetTester tester) async { testWidgets('FocusableActionDetector keeps track of focus and hover even when disabled.', (WidgetTester tester) async {
FocusManager.instance.highlightStrategy = FocusHighlightStrategy.alwaysTraditional; FocusManager.instance.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
final GlobalKey containerKey = GlobalKey(); final GlobalKey containerKey = GlobalKey();
...@@ -383,6 +392,7 @@ void main() { ...@@ -383,6 +392,7 @@ void main() {
expect(hovering, isFalse); expect(hovering, isFalse);
expect(focusing, isFalse); expect(focusing, isFalse);
}); });
testWidgets('FocusableActionDetector changes mouse cursor when hovered', (WidgetTester tester) async { testWidgets('FocusableActionDetector changes mouse cursor when hovered', (WidgetTester tester) async {
await tester.pumpWidget( await tester.pumpWidget(
MouseRegion( MouseRegion(
...@@ -415,6 +425,7 @@ void main() { ...@@ -415,6 +425,7 @@ void main() {
expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.forbidden); expect(RendererBinding.instance.mouseTracker.debugDeviceActiveCursor(1), SystemMouseCursors.forbidden);
}); });
testWidgets('Actions.invoke returns the value of Action.invoke', (WidgetTester tester) async { testWidgets('Actions.invoke returns the value of Action.invoke', (WidgetTester tester) async {
final GlobalKey containerKey = GlobalKey(); final GlobalKey containerKey = GlobalKey();
final Object sentinel = Object(); final Object sentinel = Object();
...@@ -445,6 +456,7 @@ void main() { ...@@ -445,6 +456,7 @@ void main() {
expect(identical(result, sentinel), isTrue); expect(identical(result, sentinel), isTrue);
expect(invoked, isTrue); expect(invoked, isTrue);
}); });
testWidgets('ContextAction can return null', (WidgetTester tester) async { testWidgets('ContextAction can return null', (WidgetTester tester) async {
final GlobalKey containerKey = GlobalKey(); final GlobalKey containerKey = GlobalKey();
const TestIntent intent = TestIntent(); const TestIntent intent = TestIntent();
...@@ -471,6 +483,7 @@ void main() { ...@@ -471,6 +483,7 @@ void main() {
expect(invokedDispatcher.runtimeType, equals(TestDispatcher1)); expect(invokedDispatcher.runtimeType, equals(TestDispatcher1));
expect(testAction.capturedContexts.single, containerKey.currentContext); expect(testAction.capturedContexts.single, containerKey.currentContext);
}); });
testWidgets('Disabled actions stop propagation to an ancestor', (WidgetTester tester) async { testWidgets('Disabled actions stop propagation to an ancestor', (WidgetTester tester) async {
final GlobalKey containerKey = GlobalKey(); final GlobalKey containerKey = GlobalKey();
bool invoked = false; bool invoked = false;
...@@ -775,6 +788,7 @@ void main() { ...@@ -775,6 +788,7 @@ void main() {
expect(hovering, isFalse); expect(hovering, isFalse);
expect(focusing, isFalse); expect(focusing, isFalse);
}); });
testWidgets('FocusableActionDetector shows focus highlight appropriately when focused and disabled', (WidgetTester tester) async { testWidgets('FocusableActionDetector shows focus highlight appropriately when focused and disabled', (WidgetTester tester) async {
FocusManager.instance.highlightStrategy = FocusHighlightStrategy.alwaysTraditional; FocusManager.instance.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
final GlobalKey containerKey = GlobalKey(); final GlobalKey containerKey = GlobalKey();
...@@ -805,6 +819,7 @@ void main() { ...@@ -805,6 +819,7 @@ void main() {
await tester.pump(); await tester.pump();
expect(focusing, isTrue); expect(focusing, isTrue);
}); });
testWidgets('FocusableActionDetector can be used without callbacks', (WidgetTester tester) async { testWidgets('FocusableActionDetector can be used without callbacks', (WidgetTester tester) async {
FocusManager.instance.highlightStrategy = FocusHighlightStrategy.alwaysTraditional; FocusManager.instance.highlightStrategy = FocusHighlightStrategy.alwaysTraditional;
final GlobalKey containerKey = GlobalKey(); final GlobalKey containerKey = GlobalKey();
...@@ -951,6 +966,110 @@ void main() { ...@@ -951,6 +966,110 @@ void main() {
expect(buttonNode2.hasFocus, isFalse); expect(buttonNode2.hasFocus, isFalse);
}, },
); );
testWidgets('FocusableActionDetector can exclude Focus semantics', (WidgetTester tester) async {
await tester.pumpWidget(
MaterialApp(
home: FocusableActionDetector(
child: Column(
children: <Widget>[
TextButton(
onPressed: () {},
child: const Text('Button 1'),
),
TextButton(
onPressed: () {},
child: const Text('Button 2'),
),
],
),
),
),
);
expect(
tester.getSemantics(find.byType(FocusableActionDetector)),
matchesSemantics(
scopesRoute: true,
children: <Matcher>[
// This semantic is from `Focus` widget under `FocusableActionDetector`.
matchesSemantics(
isFocusable: true,
children: <Matcher>[
matchesSemantics(
hasTapAction: true,
isButton: true,
hasEnabledState: true,
isEnabled: true,
isFocusable: true,
label: 'Button 1',
textDirection: TextDirection.ltr,
),
matchesSemantics(
hasTapAction: true,
isButton: true,
hasEnabledState: true,
isEnabled: true,
isFocusable: true,
label: 'Button 2',
textDirection: TextDirection.ltr,
),
],
),
],
),
);
// Set `includeFocusSemantics` to false to exclude semantics
// from `Focus` widget under `FocusableActionDetector`.
await tester.pumpWidget(
MaterialApp(
home: FocusableActionDetector(
includeFocusSemantics: false,
child: Column(
children: <Widget>[
TextButton(
onPressed: () {},
child: const Text('Button 1'),
),
TextButton(
onPressed: () {},
child: const Text('Button 2'),
),
],
),
),
),
);
// Semantics from the `Focus` widget will be removed.
expect(
tester.getSemantics(find.byType(FocusableActionDetector)),
matchesSemantics(
scopesRoute: true,
children: <Matcher>[
matchesSemantics(
hasTapAction: true,
isButton: true,
hasEnabledState: true,
isEnabled: true,
isFocusable: true,
label: 'Button 1',
textDirection: TextDirection.ltr,
),
matchesSemantics(
hasTapAction: true,
isButton: true,
hasEnabledState: true,
isEnabled: true,
isFocusable: true,
label: 'Button 2',
textDirection: TextDirection.ltr,
),
],
),
);
});
}); });
group('Action subclasses', () { group('Action subclasses', () {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment