Unverified Commit 947bd752 authored by Greg Spencer's avatar Greg Spencer Committed by GitHub

Fix a bug in Shortcuts with synonyms, and add tests. (#33949)

This fixes a rather embarrassing bug in ShortcutManager that happened because I forgot to add the tests I meant to add.

This fixes the bug, and adds the tests.
parent 95818d2b
......@@ -194,7 +194,7 @@ class ShortcutManager extends ChangeNotifier with DiagnosticableMixin {
// If there's not a more specific match, We also look for any keys that
// have synonyms in the map. This is for things like left and right shift
// keys mapping to just the "shift" pseudo-key.
Set<LogicalKeyboardKey> pseudoKeys;
final Set<LogicalKeyboardKey> pseudoKeys = <LogicalKeyboardKey>{};
for (LogicalKeyboardKey setKey in keySet.keys) {
final Set<LogicalKeyboardKey> synonyms = setKey.synonyms;
if (synonyms.isNotEmpty) {
......
......@@ -5,9 +5,103 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter/src/services/keyboard_key.dart';
import 'package:flutter/src/services/keyboard_maps.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart';
void sendFakeKeyEvent(Map<String, dynamic> data) {
defaultBinaryMessenger.handlePlatformMessage(
SystemChannels.keyEvent.name,
SystemChannels.keyEvent.codec.encodeMessage(data),
(ByteData data) {},
);
}
typedef PostInvokeCallback = void Function({Action action, Intent intent, FocusNode focusNode, ActionDispatcher dispatcher});
class TestAction extends CallbackAction {
const TestAction({
@required OnInvokeCallback onInvoke,
}) : assert(onInvoke != null),
super(key, onInvoke: onInvoke);
static const LocalKey key = ValueKey<Type>(TestAction);
}
class TestDispatcher extends ActionDispatcher {
const TestDispatcher({this.postInvoke});
final PostInvokeCallback postInvoke;
@override
bool invokeAction(Action action, Intent intent, {FocusNode focusNode}) {
final bool result = super.invokeAction(action, intent, focusNode: focusNode);
postInvoke?.call(action: action, intent: intent, focusNode: focusNode, dispatcher: this);
return result;
}
}
class TestIntent extends Intent {
const TestIntent() : super(TestAction.key);
}
class DoNothingAction extends Action {
const DoNothingAction({
@required OnInvokeCallback onInvoke,
}) : assert(onInvoke != null),
super(key);
static const LocalKey key = ValueKey<Type>(DoNothingAction);
@override
void invoke(FocusNode node, Intent invocation) {}
}
class DoNothingIntent extends Intent {
const DoNothingIntent() : super(DoNothingAction.key);
}
class TestShortcutManager extends ShortcutManager {
TestShortcutManager(this.keys);
List<LogicalKeyboardKey> keys;
@override
bool handleKeypress(BuildContext context, RawKeyEvent event, {LogicalKeySet keysPressed}) {
keys.add(event.logicalKey);
return super.handleKeypress(context, event, keysPressed: keysPressed);
}
}
void testKeypress(LogicalKeyboardKey key) {
assert(key.debugName != null);
int keyCode;
kAndroidToLogicalKey.forEach((int code, LogicalKeyboardKey codeKey) {
if (key == codeKey) {
keyCode = code;
}
});
assert(keyCode != null, 'Key $key not found in Android key map');
int scanCode;
kAndroidToPhysicalKey.forEach((int code, PhysicalKeyboardKey codeKey) {
if (key.debugName == codeKey.debugName) {
scanCode = code;
}
});
assert(scanCode != null, 'Physical key for $key not found in Android key map');
sendFakeKeyEvent(<String, dynamic>{
'type': 'keydown',
'keymap': 'android',
'keyCode': keyCode,
'plainCodePoint': 0,
'codePoint': 0,
'character': null,
'scanCode': scanCode,
'metaState': 0,
});
}
void main() {
group(LogicalKeySet, () {
test('$LogicalKeySet passes parameters correctly.', () {
......@@ -106,10 +200,73 @@ void main() {
'keys: {LogicalKeyboardKey#00000(keyId: "0x00000061", keyLabel: "a", debugName: "Key A"), LogicalKeyboardKey#00000(keyId: "0x00000062", keyLabel: "b", debugName: "Key B")}'));
});
});
group(ShortcutManager, () {
test('$ShortcutManager .', () {
group(Shortcuts, () {
testWidgets('$ShortcutManager handles shortcuts', (WidgetTester tester) async {
final GlobalKey containerKey = GlobalKey();
final List<LogicalKeyboardKey> pressedKeys = <LogicalKeyboardKey>[];
final TestShortcutManager testManager = TestShortcutManager(pressedKeys);
bool invoked = false;
await tester.pumpWidget(
Actions(
actions: <LocalKey, ActionFactory>{
TestAction.key: () => TestAction(onInvoke: (FocusNode node, Intent intent) {
invoked = true;
return true;
}),
},
child: Shortcuts(
manager: testManager,
shortcuts: <LogicalKeySet, Intent>{
LogicalKeySet(LogicalKeyboardKey.shift): const TestIntent(),
},
child: Focus(
autofocus: true,
child: Container(key: containerKey, width: 100, height: 100),
),
),
),
);
await tester.pump();
expect(Shortcuts.of(containerKey.currentContext), isNotNull);
testKeypress(LogicalKeyboardKey.shiftLeft);
expect(invoked, isTrue);
expect(pressedKeys, equals(<LogicalKeyboardKey>[LogicalKeyboardKey.shiftLeft]));
});
testWidgets("$Shortcuts passes to the next $Shortcuts widget if it doesn't map the key", (WidgetTester tester) async {
final GlobalKey containerKey = GlobalKey();
final List<LogicalKeyboardKey> pressedKeys = <LogicalKeyboardKey>[];
final TestShortcutManager testManager = TestShortcutManager(pressedKeys);
bool invoked = false;
await tester.pumpWidget(
Shortcuts(
manager: testManager,
shortcuts: <LogicalKeySet, Intent>{
LogicalKeySet(LogicalKeyboardKey.shift): const TestIntent(),
},
child: Actions(
actions: <LocalKey, ActionFactory>{
TestAction.key: () => TestAction(onInvoke: (FocusNode node, Intent intent) {
invoked = true;
return true;
}),
},
child: Shortcuts(
shortcuts: <LogicalKeySet, Intent>{
LogicalKeySet(LogicalKeyboardKey.keyA): const DoNothingIntent(),
},
child: Focus(
autofocus: true,
child: Container(key: containerKey, width: 100, height: 100),
),
),
),
),
);
await tester.pump();
expect(Shortcuts.of(containerKey.currentContext), isNotNull);
testKeypress(LogicalKeyboardKey.shiftLeft);
expect(invoked, isTrue);
expect(pressedKeys, equals(<LogicalKeyboardKey>[LogicalKeyboardKey.shiftLeft]));
});
});
group(Shortcuts, () {});
}
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