diff --git a/packages/flutter/lib/src/widgets/shortcuts.dart b/packages/flutter/lib/src/widgets/shortcuts.dart index 9fdbe43deb..a40263c020 100644 --- a/packages/flutter/lib/src/widgets/shortcuts.dart +++ b/packages/flutter/lib/src/widgets/shortcuts.dart @@ -211,6 +211,16 @@ abstract class ShortcutActivator { /// modifier key is pressed when the side variation is not important. bool accepts(RawKeyEvent event, RawKeyboard state); + /// Returns true if the event and keyboard state would cause this + /// [ShortcutActivator] to be activated. + /// + /// If the keyboard `state` isn't supplied, then it defaults to using + /// [RawKeyboard.instance]. + static bool isActivatedBy(ShortcutActivator activator, RawKeyEvent event) { + return (activator.triggers?.contains(event.logicalKey) ?? true) + && activator.accepts(event, RawKeyboard.instance); + } + /// Returns a description of the key set that is short and readable. /// /// Intended to be used in debug mode for logging purposes. @@ -1016,11 +1026,9 @@ class CallbackShortcuts extends StatelessWidget { // throws, by providing the activator and event as arguments that will appear // in the stack trace. bool _applyKeyBinding(ShortcutActivator activator, RawKeyEvent event) { - if (activator.triggers?.contains(event.logicalKey) ?? true) { - if (activator.accepts(event, RawKeyboard.instance)) { - bindings[activator]!.call(); - return true; - } + if (ShortcutActivator.isActivatedBy(activator, event)) { + bindings[activator]!.call(); + return true; } return false; } diff --git a/packages/flutter/test/widgets/shortcuts_test.dart b/packages/flutter/test/widgets/shortcuts_test.dart index 39f5090571..16b8787b97 100644 --- a/packages/flutter/test/widgets/shortcuts_test.dart +++ b/packages/flutter/test/widgets/shortcuts_test.dart @@ -103,8 +103,8 @@ Widget activatorTester( if (hasSecond) TestIntent2: TestAction(onInvoke: (Intent intent) { onInvoke2(intent); - return null; - }), + return null; + }), }, child: Shortcuts( shortcuts: { @@ -321,6 +321,30 @@ void main() { ); }); + testWidgets('isActivatedBy works as expected', (WidgetTester tester) async { + // Collect some key events to use for testing. + final List events = []; + await tester.pumpWidget( + Focus( + autofocus: true, + onKey: (FocusNode node, RawKeyEvent event) { + events.add(event); + return KeyEventResult.ignored; + }, + child: const SizedBox(), + ), + ); + + final LogicalKeySet set = LogicalKeySet(LogicalKeyboardKey.keyA, LogicalKeyboardKey.control); + + await tester.sendKeyDownEvent(LogicalKeyboardKey.controlLeft); + await tester.sendKeyDownEvent(LogicalKeyboardKey.keyA); + expect(ShortcutActivator.isActivatedBy(set, events[0]), isTrue); + await tester.sendKeyUpEvent(LogicalKeyboardKey.keyA); + await tester.sendKeyUpEvent(LogicalKeyboardKey.controlLeft); + expect(ShortcutActivator.isActivatedBy(set, events[0]), isFalse); + }); + test('LogicalKeySet diagnostics work.', () { final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder(); @@ -541,6 +565,30 @@ void main() { expect(RawKeyboard.instance.keysPressed, isEmpty); }); + testWidgets('isActivatedBy works as expected', (WidgetTester tester) async { + // Collect some key events to use for testing. + final List events = []; + await tester.pumpWidget( + Focus( + autofocus: true, + onKey: (FocusNode node, RawKeyEvent event) { + events.add(event); + return KeyEventResult.ignored; + }, + child: const SizedBox(), + ), + ); + + const SingleActivator singleActivator = SingleActivator(LogicalKeyboardKey.keyA, control: true); + + await tester.sendKeyDownEvent(LogicalKeyboardKey.controlLeft); + await tester.sendKeyDownEvent(LogicalKeyboardKey.keyA); + await tester.sendKeyUpEvent(LogicalKeyboardKey.keyA); + expect(ShortcutActivator.isActivatedBy(singleActivator, events[1]), isTrue); + await tester.sendKeyUpEvent(LogicalKeyboardKey.controlLeft); + expect(ShortcutActivator.isActivatedBy(singleActivator, events[1]), isFalse); + }); + group('diagnostics.', () { test('single key', () { final DiagnosticPropertiesBuilder builder = DiagnosticPropertiesBuilder(); @@ -1167,6 +1215,28 @@ void main() { expect(invoked, 2); invoked = 0; }, variant: KeySimulatorTransitModeVariant.all()); + + + testWidgets('isActivatedBy works as expected', (WidgetTester tester) async { + // Collect some key events to use for testing. + final List events = []; + await tester.pumpWidget( + Focus( + autofocus: true, + onKey: (FocusNode node, RawKeyEvent event) { + events.add(event); + return KeyEventResult.ignored; + }, + child: const SizedBox(), + ), + ); + + const CharacterActivator characterActivator = CharacterActivator('a'); + + await tester.sendKeyDownEvent(LogicalKeyboardKey.keyA); + await tester.sendKeyUpEvent(LogicalKeyboardKey.keyA); + expect(ShortcutActivator.isActivatedBy(characterActivator, events[0]), isTrue); + }); }); group('CallbackShortcuts', () {