blob: 87fe0edfa81b593efa3575c31ac568fbdb6de7b9 [file] [log] [blame]
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart';
class _ModifierCheck {
const _ModifierCheck(this.key, this.side);
final ModifierKey key;
final KeyboardSide side;
}
void main() {
group('RawKeyboard', () {
testWidgets('The correct character is produced', (WidgetTester tester) async {
for (final String platform in <String>['linux', 'android', 'macos', 'fuchsia', 'windows']) {
String character = '';
void handleKey(RawKeyEvent event) {
expect(event.character, equals(character), reason: 'on $platform');
}
RawKeyboard.instance.addListener(handleKey);
character = 'a';
await simulateKeyDownEvent(LogicalKeyboardKey.keyA, platform: platform);
character = '`';
await simulateKeyDownEvent(LogicalKeyboardKey.backquote, platform: platform);
RawKeyboard.instance.removeListener(handleKey);
}
});
testWidgets('No character is produced for non-printables', (WidgetTester tester) async {
for (final String platform in <String>['linux', 'android', 'macos', 'fuchsia', 'windows']) {
void handleKey(RawKeyEvent event) {
expect(event.character, isNull, reason: 'on $platform');
}
RawKeyboard.instance.addListener(handleKey);
await simulateKeyDownEvent(LogicalKeyboardKey.shiftLeft, platform: platform);
RawKeyboard.instance.removeListener(handleKey);
}
});
testWidgets('keysPressed is maintained', (WidgetTester tester) async {
for (final String platform in <String>['linux', 'android', 'macos', 'fuchsia', 'windows']) {
RawKeyboard.instance.clearKeysPressed();
expect(RawKeyboard.instance.keysPressed, isEmpty, reason: 'on $platform');
await simulateKeyDownEvent(LogicalKeyboardKey.shiftLeft, platform: platform);
expect(
RawKeyboard.instance.keysPressed,
equals(
<LogicalKeyboardKey>{ LogicalKeyboardKey.shiftLeft,
// Linux doesn't have a concept of left/right keys, so they're all
// shown as down when either is pressed.
if (platform == 'linux') LogicalKeyboardKey.shiftRight,
},
),
reason: 'on $platform',
);
await simulateKeyDownEvent(LogicalKeyboardKey.controlLeft, platform: platform);
expect(
RawKeyboard.instance.keysPressed,
equals(
<LogicalKeyboardKey>{
LogicalKeyboardKey.shiftLeft,
if (platform == 'linux') LogicalKeyboardKey.shiftRight,
LogicalKeyboardKey.controlLeft,
if (platform == 'linux') LogicalKeyboardKey.controlRight,
},
),
reason: 'on $platform',
);
await simulateKeyDownEvent(LogicalKeyboardKey.keyA, platform: platform);
expect(
RawKeyboard.instance.keysPressed,
equals(
<LogicalKeyboardKey>{
LogicalKeyboardKey.shiftLeft,
if (platform == 'linux') LogicalKeyboardKey.shiftRight,
LogicalKeyboardKey.controlLeft,
if (platform == 'linux') LogicalKeyboardKey.controlRight,
LogicalKeyboardKey.keyA,
},
),
reason: 'on $platform',
);
await simulateKeyUpEvent(LogicalKeyboardKey.keyA, platform: platform);
expect(
RawKeyboard.instance.keysPressed,
equals(
<LogicalKeyboardKey>{
LogicalKeyboardKey.shiftLeft,
if (platform == 'linux') LogicalKeyboardKey.shiftRight,
LogicalKeyboardKey.controlLeft,
if (platform == 'linux') LogicalKeyboardKey.controlRight,
},
),
reason: 'on $platform',
);
await simulateKeyUpEvent(LogicalKeyboardKey.controlLeft, platform: platform);
expect(
RawKeyboard.instance.keysPressed,
equals(
<LogicalKeyboardKey>{
LogicalKeyboardKey.shiftLeft,
if (platform == 'linux') LogicalKeyboardKey.shiftRight,
},
),
reason: 'on $platform',
);
await simulateKeyUpEvent(LogicalKeyboardKey.shiftLeft, platform: platform);
expect(RawKeyboard.instance.keysPressed, isEmpty, reason: 'on $platform');
// The Fn key isn't mapped on linux or Windows.
if (platform != 'linux' && platform != 'windows') {
await simulateKeyDownEvent(LogicalKeyboardKey.fn, platform: platform);
expect(
RawKeyboard.instance.keysPressed,
equals(
<LogicalKeyboardKey>{
if (platform != 'macos') LogicalKeyboardKey.fn,
},
),
reason: 'on $platform');
await simulateKeyDownEvent(LogicalKeyboardKey.f12, platform: platform);
expect(
RawKeyboard.instance.keysPressed,
equals(
<LogicalKeyboardKey>{
if (platform != 'macos') LogicalKeyboardKey.fn,
LogicalKeyboardKey.f12,
},
),
reason: 'on $platform',
);
await simulateKeyUpEvent(LogicalKeyboardKey.fn, platform: platform);
expect(
RawKeyboard.instance.keysPressed,
equals(
<LogicalKeyboardKey>{ LogicalKeyboardKey.f12 },
),
reason: 'on $platform',
);
await simulateKeyUpEvent(LogicalKeyboardKey.f12, platform: platform);
expect(RawKeyboard.instance.keysPressed, isEmpty, reason: 'on $platform');
}
}
}, skip: isBrowser); // https://github.com/flutter/flutter/issues/61021
testWidgets('keysPressed is correct when modifier is released before key', (WidgetTester tester) async {
for (final String platform in <String>['linux', 'android', 'macos', 'fuchsia', 'windows']) {
RawKeyboard.instance.clearKeysPressed();
expect(RawKeyboard.instance.keysPressed, isEmpty, reason: 'on $platform');
await simulateKeyDownEvent(LogicalKeyboardKey.shiftLeft, platform: platform, physicalKey: PhysicalKeyboardKey.shiftLeft);
expect(
RawKeyboard.instance.keysPressed,
equals(
<LogicalKeyboardKey>{
LogicalKeyboardKey.shiftLeft,
// Linux doesn't have a concept of left/right keys, so they're all
// shown as down when either is pressed.
if (platform == 'linux') LogicalKeyboardKey.shiftRight,
},
),
reason: 'on $platform',
);
// TODO(gspencergoog): Switch to capital A when the new key event code
// is finished that can simulate real keys.
// https://github.com/flutter/flutter/issues/33521
// This should really be done with a simulated capital A, but the event
// simulation code doesn't really support that, since it only can
// simulate events that appear in the key maps (and capital letters
// don't appear there).
await simulateKeyDownEvent(LogicalKeyboardKey.keyA, platform: platform, physicalKey: PhysicalKeyboardKey.keyA);
expect(
RawKeyboard.instance.keysPressed,
equals(
<LogicalKeyboardKey>{
LogicalKeyboardKey.shiftLeft,
if (platform == 'linux') LogicalKeyboardKey.shiftRight,
LogicalKeyboardKey.keyA,
},
),
reason: 'on $platform',
);
await simulateKeyUpEvent(LogicalKeyboardKey.shiftLeft, platform: platform, physicalKey: PhysicalKeyboardKey.shiftLeft);
expect(
RawKeyboard.instance.keysPressed,
equals(
<LogicalKeyboardKey>{
LogicalKeyboardKey.keyA,
},
),
reason: 'on $platform',
);
await simulateKeyUpEvent(LogicalKeyboardKey.keyA, platform: platform, physicalKey: PhysicalKeyboardKey.keyA);
expect(RawKeyboard.instance.keysPressed, isEmpty, reason: 'on $platform');
}
});
testWidgets('keysPressed modifiers are synchronized with key events on macOS', (WidgetTester tester) async {
expect(RawKeyboard.instance.keysPressed, isEmpty);
// Generate the data for a regular key down event.
final Map<String, dynamic> data = KeyEventSimulator.getKeyData(
LogicalKeyboardKey.keyA,
platform: 'macos',
isDown: true,
);
// Change the modifiers so that they show the shift key as already down
// when this event is received, but it's not in keysPressed yet.
data['modifiers'] |= RawKeyEventDataMacOs.modifierLeftShift | RawKeyEventDataMacOs.modifierShift;
// dispatch the modified data.
await ServicesBinding.instance!.defaultBinaryMessenger.handlePlatformMessage(
SystemChannels.keyEvent.name,
SystemChannels.keyEvent.codec.encodeMessage(data),
(ByteData? data) {},
);
expect(
RawKeyboard.instance.keysPressed,
equals(
<LogicalKeyboardKey>{LogicalKeyboardKey.shiftLeft, LogicalKeyboardKey.keyA},
),
);
});
testWidgets('keysPressed modifiers are synchronized with key events on Windows', (WidgetTester tester) async {
expect(RawKeyboard.instance.keysPressed, isEmpty);
// Generate the data for a regular key down event.
final Map<String, dynamic> data = KeyEventSimulator.getKeyData(
LogicalKeyboardKey.keyA,
platform: 'windows',
isDown: true,
);
// Change the modifiers so that they show the shift key as already down
// when this event is received, but it's not in keysPressed yet.
data['modifiers'] |= RawKeyEventDataWindows.modifierLeftShift | RawKeyEventDataWindows.modifierShift;
// dispatch the modified data.
await ServicesBinding.instance!.defaultBinaryMessenger.handlePlatformMessage(
SystemChannels.keyEvent.name,
SystemChannels.keyEvent.codec.encodeMessage(data),
(ByteData? data) {},
);
expect(
RawKeyboard.instance.keysPressed,
equals(
<LogicalKeyboardKey>{LogicalKeyboardKey.shiftLeft, LogicalKeyboardKey.keyA},
),
);
});
testWidgets('keysPressed modifiers are synchronized with key events on android', (WidgetTester tester) async {
expect(RawKeyboard.instance.keysPressed, isEmpty);
// Generate the data for a regular key down event.
final Map<String, dynamic> data = KeyEventSimulator.getKeyData(
LogicalKeyboardKey.keyA,
platform: 'android',
isDown: true,
);
// Change the modifiers so that they show the shift key as already down
// when this event is received, but it's not in keysPressed yet.
data['metaState'] |= RawKeyEventDataAndroid.modifierLeftShift | RawKeyEventDataAndroid.modifierShift;
// dispatch the modified data.
await ServicesBinding.instance!.defaultBinaryMessenger.handlePlatformMessage(
SystemChannels.keyEvent.name,
SystemChannels.keyEvent.codec.encodeMessage(data),
(ByteData? data) {},
);
expect(
RawKeyboard.instance.keysPressed,
equals(
<LogicalKeyboardKey>{LogicalKeyboardKey.shiftLeft, LogicalKeyboardKey.keyA},
),
);
});
testWidgets('keysPressed modifiers are synchronized with key events on fuchsia', (WidgetTester tester) async {
expect(RawKeyboard.instance.keysPressed, isEmpty);
// Generate the data for a regular key down event.
final Map<String, dynamic> data = KeyEventSimulator.getKeyData(
LogicalKeyboardKey.keyA,
platform: 'fuchsia',
isDown: true,
);
// Change the modifiers so that they show the shift key as already down
// when this event is received, but it's not in keysPressed yet.
data['modifiers'] |= RawKeyEventDataFuchsia.modifierLeftShift;
// dispatch the modified data.
await ServicesBinding.instance!.defaultBinaryMessenger.handlePlatformMessage(
SystemChannels.keyEvent.name,
SystemChannels.keyEvent.codec.encodeMessage(data),
(ByteData? data) {},
);
expect(
RawKeyboard.instance.keysPressed,
equals(
<LogicalKeyboardKey>{LogicalKeyboardKey.shiftLeft, LogicalKeyboardKey.keyA},
),
);
});
testWidgets('keysPressed modifiers are synchronized with key events on Linux GLFW', (WidgetTester tester) async {
expect(RawKeyboard.instance.keysPressed, isEmpty);
// Generate the data for a regular key down event.
final Map<String, dynamic> data = KeyEventSimulator.getKeyData(
LogicalKeyboardKey.keyA,
platform: 'linux',
isDown: true,
);
// Change the modifiers so that they show the shift key as already down
// when this event is received, but it's not in keysPressed yet.
data['modifiers'] |= GLFWKeyHelper.modifierShift;
// dispatch the modified data.
await ServicesBinding.instance!.defaultBinaryMessenger.handlePlatformMessage(
SystemChannels.keyEvent.name,
SystemChannels.keyEvent.codec.encodeMessage(data),
(ByteData? data) {},
);
expect(
RawKeyboard.instance.keysPressed,
equals(
<LogicalKeyboardKey>{
LogicalKeyboardKey.shiftLeft,
// Linux doesn't have a concept of left/right keys, so they're all
// shown as down when either is pressed.
LogicalKeyboardKey.shiftRight,
LogicalKeyboardKey.keyA,
},
),
);
});
testWidgets('sided modifiers without a side set return all sides on Android', (WidgetTester tester) async {
expect(RawKeyboard.instance.keysPressed, isEmpty);
// Generate the data for a regular key down event.
final Map<String, dynamic> data = KeyEventSimulator.getKeyData(
LogicalKeyboardKey.keyA,
platform: 'android',
isDown: true,
);
// Set only the generic "down" modifier, without setting a side.
data['metaState'] |=
RawKeyEventDataAndroid.modifierShift |
RawKeyEventDataAndroid.modifierAlt |
RawKeyEventDataAndroid.modifierControl |
RawKeyEventDataAndroid.modifierMeta;
// dispatch the modified data.
await ServicesBinding.instance!.defaultBinaryMessenger.handlePlatformMessage(
SystemChannels.keyEvent.name,
SystemChannels.keyEvent.codec.encodeMessage(data),
(ByteData? data) {},
);
expect(
RawKeyboard.instance.keysPressed,
equals(
<LogicalKeyboardKey>{
LogicalKeyboardKey.shiftLeft,
LogicalKeyboardKey.shiftRight,
LogicalKeyboardKey.altLeft,
LogicalKeyboardKey.altRight,
LogicalKeyboardKey.controlLeft,
LogicalKeyboardKey.controlRight,
LogicalKeyboardKey.metaLeft,
LogicalKeyboardKey.metaRight,
LogicalKeyboardKey.keyA,
},
),
);
});
testWidgets('sided modifiers without a side set return all sides on macOS', (WidgetTester tester) async {
expect(RawKeyboard.instance.keysPressed, isEmpty);
// Generate the data for a regular key down event.
final Map<String, dynamic> data = KeyEventSimulator.getKeyData(
LogicalKeyboardKey.keyA,
platform: 'macos',
isDown: true,
);
// Set only the generic "shift down" modifier, without setting a side.
data['modifiers'] |=
RawKeyEventDataMacOs.modifierShift |
RawKeyEventDataMacOs.modifierOption |
RawKeyEventDataMacOs.modifierCommand |
RawKeyEventDataMacOs.modifierControl;
// dispatch the modified data.
await ServicesBinding.instance!.defaultBinaryMessenger.handlePlatformMessage(
SystemChannels.keyEvent.name,
SystemChannels.keyEvent.codec.encodeMessage(data),
(ByteData? data) {},
);
expect(
RawKeyboard.instance.keysPressed,
equals(
<LogicalKeyboardKey>{
LogicalKeyboardKey.shiftLeft,
LogicalKeyboardKey.shiftRight,
LogicalKeyboardKey.altLeft,
LogicalKeyboardKey.altRight,
LogicalKeyboardKey.controlLeft,
LogicalKeyboardKey.controlRight,
LogicalKeyboardKey.metaLeft,
LogicalKeyboardKey.metaRight,
LogicalKeyboardKey.keyA,
},
),
);
});
testWidgets('sided modifiers without a side set return all sides on Windows', (WidgetTester tester) async {
expect(RawKeyboard.instance.keysPressed, isEmpty);
// Generate the data for a regular key down event.
final Map<String, dynamic> data = KeyEventSimulator.getKeyData(
LogicalKeyboardKey.keyA,
platform: 'windows',
isDown: true,
);
// Set only the generic "shift down" modifier, without setting a side.
// Windows doesn't have a concept of "either" for the Windows (meta) key.
data['modifiers'] |=
RawKeyEventDataWindows.modifierShift |
RawKeyEventDataWindows.modifierAlt |
RawKeyEventDataWindows.modifierControl;
// dispatch the modified data.
await ServicesBinding.instance!.defaultBinaryMessenger.handlePlatformMessage(
SystemChannels.keyEvent.name,
SystemChannels.keyEvent.codec.encodeMessage(data),
(ByteData? data) {},
);
expect(
RawKeyboard.instance.keysPressed,
equals(
<LogicalKeyboardKey>{
LogicalKeyboardKey.shiftLeft,
LogicalKeyboardKey.shiftRight,
LogicalKeyboardKey.altLeft,
LogicalKeyboardKey.altRight,
LogicalKeyboardKey.controlLeft,
LogicalKeyboardKey.controlRight,
LogicalKeyboardKey.keyA,
},
),
);
});
testWidgets('sided modifiers without a side set return all sides on Linux GLFW', (WidgetTester tester) async {
expect(RawKeyboard.instance.keysPressed, isEmpty);
// Generate the data for a regular key down event.
final Map<String, dynamic> data = KeyEventSimulator.getKeyData(
LogicalKeyboardKey.keyA,
platform: 'linux',
isDown: true,
);
// Set only the generic "shift down" modifier, without setting a side.
// Windows doesn't have a concept of "either" for the Windows (meta) key.
data['modifiers'] |=
GLFWKeyHelper.modifierShift |
GLFWKeyHelper.modifierAlt |
GLFWKeyHelper.modifierControl |
GLFWKeyHelper.modifierMeta;
// dispatch the modified data.
await ServicesBinding.instance!.defaultBinaryMessenger.handlePlatformMessage(
SystemChannels.keyEvent.name,
SystemChannels.keyEvent.codec.encodeMessage(data),
(ByteData? data) {},
);
expect(
RawKeyboard.instance.keysPressed,
equals(
<LogicalKeyboardKey>{
LogicalKeyboardKey.shiftLeft,
LogicalKeyboardKey.shiftRight,
LogicalKeyboardKey.altLeft,
LogicalKeyboardKey.altRight,
LogicalKeyboardKey.controlLeft,
LogicalKeyboardKey.controlRight,
LogicalKeyboardKey.metaLeft,
LogicalKeyboardKey.metaRight,
LogicalKeyboardKey.keyA,
},
),
);
});
testWidgets('RawKeyboard asserts if no keys are in keysPressed after receiving a key down event', (WidgetTester tester) async {
FlutterErrorDetails? errorDetails;
final FlutterExceptionHandler? oldHandler = FlutterError.onError;
FlutterError.onError = (FlutterErrorDetails details) {
errorDetails = details;
};
try {
await ServicesBinding.instance!.defaultBinaryMessenger
.handlePlatformMessage(
SystemChannels.keyEvent.name,
SystemChannels.keyEvent.codec.encodeMessage(const <String, dynamic>{
'type': 'keydown',
'keymap': 'android',
'keyCode': 0x3b, // Left shift key keyCode
'scanCode': 0x2a,
'metaState': 0x0, // No shift key metaState set!
'source': 0x101,
'deviceId': 1,
}),
(ByteData? data) {},
);
} finally {
FlutterError.onError = oldHandler;
}
expect(errorDetails, isNotNull);
expect(errorDetails!.stack, isNotNull);
final String fullErrorMessage = errorDetails.toString().replaceAll('\n', ' ');
expect(fullErrorMessage, contains('Attempted to send a key down event when no keys are in keysPressed'));
});
});
group('RawKeyEventDataAndroid', () {
const Map<int, _ModifierCheck> modifierTests = <int, _ModifierCheck>{
RawKeyEventDataAndroid.modifierAlt | RawKeyEventDataAndroid.modifierLeftAlt: _ModifierCheck(ModifierKey.altModifier, KeyboardSide.left),
RawKeyEventDataAndroid.modifierAlt | RawKeyEventDataAndroid.modifierRightAlt: _ModifierCheck(ModifierKey.altModifier, KeyboardSide.right),
RawKeyEventDataAndroid.modifierShift | RawKeyEventDataAndroid.modifierLeftShift: _ModifierCheck(ModifierKey.shiftModifier, KeyboardSide.left),
RawKeyEventDataAndroid.modifierShift | RawKeyEventDataAndroid.modifierRightShift: _ModifierCheck(ModifierKey.shiftModifier, KeyboardSide.right),
RawKeyEventDataAndroid.modifierSym: _ModifierCheck(ModifierKey.symbolModifier, KeyboardSide.all),
RawKeyEventDataAndroid.modifierFunction: _ModifierCheck(ModifierKey.functionModifier, KeyboardSide.all),
RawKeyEventDataAndroid.modifierControl | RawKeyEventDataAndroid.modifierLeftControl: _ModifierCheck(ModifierKey.controlModifier, KeyboardSide.left),
RawKeyEventDataAndroid.modifierControl | RawKeyEventDataAndroid.modifierRightControl: _ModifierCheck(ModifierKey.controlModifier, KeyboardSide.right),
RawKeyEventDataAndroid.modifierMeta | RawKeyEventDataAndroid.modifierLeftMeta: _ModifierCheck(ModifierKey.metaModifier, KeyboardSide.left),
RawKeyEventDataAndroid.modifierMeta | RawKeyEventDataAndroid.modifierRightMeta: _ModifierCheck(ModifierKey.metaModifier, KeyboardSide.right),
RawKeyEventDataAndroid.modifierCapsLock: _ModifierCheck(ModifierKey.capsLockModifier, KeyboardSide.all),
RawKeyEventDataAndroid.modifierNumLock: _ModifierCheck(ModifierKey.numLockModifier, KeyboardSide.all),
RawKeyEventDataAndroid.modifierScrollLock: _ModifierCheck(ModifierKey.scrollLockModifier, KeyboardSide.all),
};
test('modifier keys are recognized individually', () {
for (final int modifier in modifierTests.keys) {
final RawKeyEvent event = RawKeyEvent.fromMessage(<String, dynamic>{
'type': 'keydown',
'keymap': 'android',
'keyCode': 0x04,
'plainCodePoint': 0x64,
'codePoint': 0x44,
'scanCode': 0x20,
'metaState': modifier,
'source': 0x101, // Keyboard source.
'deviceId': 1,
});
final RawKeyEventDataAndroid data = event.data as RawKeyEventDataAndroid;
for (final ModifierKey key in ModifierKey.values) {
if (modifierTests[modifier]!.key == key) {
expect(
data.isModifierPressed(key, side: modifierTests[modifier]!.side),
isTrue,
reason: "$key should be pressed with metaState $modifier, but isn't.",
);
expect(data.getModifierSide(key), equals(modifierTests[modifier]!.side));
} else {
expect(
data.isModifierPressed(key, side: modifierTests[modifier]!.side),
isFalse,
reason: '$key should not be pressed with metaState $modifier.',
);
}
}
}
});
test('modifier keys are recognized when combined', () {
for (final int modifier in modifierTests.keys) {
if (modifier == RawKeyEventDataAndroid.modifierFunction) {
// No need to combine function key with itself.
continue;
}
final RawKeyEvent event = RawKeyEvent.fromMessage(<String, dynamic>{
'type': 'keydown',
'keymap': 'android',
'keyCode': 0x04,
'plainCodePoint': 0x64,
'codePoint': 0x44,
'scanCode': 0x20,
'metaState': modifier | RawKeyEventDataAndroid.modifierFunction,
'source': 0x101, // Keyboard source.
'deviceId': 1,
});
final RawKeyEventDataAndroid data = event.data as RawKeyEventDataAndroid;
for (final ModifierKey key in ModifierKey.values) {
if (modifierTests[modifier]!.key == key || key == ModifierKey.functionModifier) {
expect(
data.isModifierPressed(key, side: modifierTests[modifier]!.side),
isTrue,
reason: '$key should be pressed with metaState $modifier '
"and additional key ${RawKeyEventDataAndroid.modifierFunction}, but isn't.",
);
if (key != ModifierKey.functionModifier) {
expect(data.getModifierSide(key), equals(modifierTests[modifier]!.side));
} else {
expect(data.getModifierSide(key), equals(KeyboardSide.all));
}
} else {
expect(
data.isModifierPressed(key, side: modifierTests[modifier]!.side),
isFalse,
reason: '$key should not be pressed with metaState $modifier '
'and additional key ${RawKeyEventDataAndroid.modifierFunction}.',
);
}
}
}
});
test('Printable keyboard keys are correctly translated', () {
final RawKeyEvent keyAEvent = RawKeyEvent.fromMessage(<String, dynamic>{
'type': 'keydown',
'keymap': 'android',
'keyCode': 29,
'plainCodePoint': 'a'.codeUnitAt(0),
'codePoint': 'A'.codeUnitAt(0),
'character': 'A',
'scanCode': 30,
'metaState': 0x0,
'source': 0x101, // Keyboard source.
'deviceId': 1,
});
final RawKeyEventDataAndroid data = keyAEvent.data as RawKeyEventDataAndroid;
expect(data.physicalKey, equals(PhysicalKeyboardKey.keyA));
expect(data.logicalKey, equals(LogicalKeyboardKey.keyA));
expect(data.keyLabel, equals('a'));
});
test('Control keyboard keys are correctly translated', () {
final RawKeyEvent escapeKeyEvent = RawKeyEvent.fromMessage(const <String, dynamic>{
'type': 'keydown',
'keymap': 'android',
'keyCode': 111,
'codePoint': 0,
'scanCode': 1,
'metaState': 0x0,
'source': 0x101, // Keyboard source.
'deviceId': 1,
});
final RawKeyEventDataAndroid data = escapeKeyEvent.data as RawKeyEventDataAndroid;
expect(data.physicalKey, equals(PhysicalKeyboardKey.escape));
expect(data.logicalKey, equals(LogicalKeyboardKey.escape));
expect(data.keyLabel, isEmpty);
});
test('Modifier keyboard keys are correctly translated', () {
final RawKeyEvent shiftLeftKeyEvent = RawKeyEvent.fromMessage(const <String, dynamic>{
'type': 'keydown',
'keymap': 'android',
'keyCode': 59,
'plainCodePoint': 0,
'codePoint': 0,
'scanCode': 42,
'metaState': RawKeyEventDataAndroid.modifierLeftShift,
'source': 0x101, // Keyboard source.
'deviceId': 1,
});
final RawKeyEventDataAndroid data = shiftLeftKeyEvent.data as RawKeyEventDataAndroid;
expect(data.physicalKey, equals(PhysicalKeyboardKey.shiftLeft));
expect(data.logicalKey, equals(LogicalKeyboardKey.shiftLeft));
expect(data.keyLabel, isEmpty);
});
test('DPAD keys from a joystick give physical key mappings', () {
final RawKeyEvent joystickDpadDown = RawKeyEvent.fromMessage(const <String, dynamic>{
'type': 'keydown',
'keymap': 'android',
'keyCode': 20,
'plainCodePoint': 0,
'codePoint': 0,
'scanCode': 0,
'metaState': 0,
'source': 0x1000010, // Joystick source.
'deviceId': 1,
});
final RawKeyEventDataAndroid data = joystickDpadDown.data as RawKeyEventDataAndroid;
expect(data.physicalKey, equals(PhysicalKeyboardKey.arrowDown));
expect(data.logicalKey, equals(LogicalKeyboardKey.arrowDown));
expect(data.keyLabel, isEmpty);
});
test('Arrow keys from a keyboard give correct physical key mappings', () {
final RawKeyEvent joystickDpadDown = RawKeyEvent.fromMessage(const <String, dynamic>{
'type': 'keydown',
'keymap': 'android',
'keyCode': 20,
'plainCodePoint': 0,
'codePoint': 0,
'scanCode': 108,
'metaState': 0,
'source': 0x101, // Keyboard source.
});
final RawKeyEventDataAndroid data = joystickDpadDown.data as RawKeyEventDataAndroid;
expect(data.physicalKey, equals(PhysicalKeyboardKey.arrowDown));
expect(data.logicalKey, equals(LogicalKeyboardKey.arrowDown));
expect(data.keyLabel, isEmpty);
});
test('DPAD center from a game pad gives physical key mappings', () {
final RawKeyEvent joystickDpadCenter = RawKeyEvent.fromMessage(const <String, dynamic>{
'type': 'keydown',
'keymap': 'android',
'keyCode': 23, // DPAD_CENTER code.
'plainCodePoint': 0,
'codePoint': 0,
'scanCode': 317, // Left side thumb joystick center click button.
'metaState': 0,
'source': 0x501, // Gamepad and keyboard source.
'deviceId': 1,
});
final RawKeyEventDataAndroid data = joystickDpadCenter.data as RawKeyEventDataAndroid;
expect(data.physicalKey, equals(PhysicalKeyboardKey.gameButtonThumbLeft));
expect(data.logicalKey, equals(LogicalKeyboardKey.select));
expect(data.keyLabel, isEmpty);
});
test('Device id is read from message', () {
final RawKeyEvent joystickDpadCenter = RawKeyEvent.fromMessage(const <String, dynamic>{
'type': 'keydown',
'keymap': 'android',
'keyCode': 23, // DPAD_CENTER code.
'plainCodePoint': 0,
'codePoint': 0,
'scanCode': 317, // Left side thumb joystick center click button.
'metaState': 0,
'source': 0x501, // Gamepad and keyboard source.
'deviceId': 10,
});
final RawKeyEventDataAndroid data = joystickDpadCenter.data as RawKeyEventDataAndroid;
expect(data.deviceId, equals(10));
});
test('Repeat count is passed correctly', () {
final RawKeyEvent repeatCountEvent = RawKeyEvent.fromMessage(<String, dynamic>{
'type': 'keydown',
'keymap': 'android',
'keyCode': 29,
'plainCodePoint': 'a'.codeUnitAt(0),
'codePoint': 'A'.codeUnitAt(0),
'character': 'A',
'scanCode': 30,
'metaState': 0x0,
'source': 0x101, // Keyboard source.
'repeatCount': 42,
});
final RawKeyEventDataAndroid data = repeatCountEvent.data as RawKeyEventDataAndroid;
expect(data.repeatCount, equals(42));
});
testWidgets('Key events are responded to correctly.', (WidgetTester tester) async {
expect(RawKeyboard.instance.keysPressed, isEmpty);
// Generate the data for a regular key down event.
final Map<String, dynamic> data = KeyEventSimulator.getKeyData(
LogicalKeyboardKey.keyA,
platform: 'android',
isDown: true,
);
Map<String, dynamic>? message;
await ServicesBinding.instance!.defaultBinaryMessenger.handlePlatformMessage(
SystemChannels.keyEvent.name,
SystemChannels.keyEvent.codec.encodeMessage(data),
(ByteData? data) {
message = SystemChannels.keyEvent.codec.decodeMessage(data) as Map<String, dynamic>;
},
);
expect(message, equals(<String, dynamic>{ 'handled': false }));
// Set up a widget that will receive focused text events.
final FocusNode focusNode = FocusNode(debugLabel: 'Test Node');
await tester.pumpWidget(
Focus(
focusNode: focusNode,
onKey: (FocusNode node, RawKeyEvent event) {
return true; // handle all events.
},
child: const SizedBox(),
),
);
focusNode.requestFocus();
await tester.pump();
await ServicesBinding.instance!.defaultBinaryMessenger.handlePlatformMessage(
SystemChannels.keyEvent.name,
SystemChannels.keyEvent.codec.encodeMessage(data),
(ByteData? data) {
message = SystemChannels.keyEvent.codec.decodeMessage(data) as Map<String, dynamic>;
},
);
expect(message, equals(<String, dynamic>{ 'handled': true }));
ServicesBinding.instance!.defaultBinaryMessenger.setMockMessageHandler(SystemChannels.keyEvent.name, null);
});
});
group('RawKeyEventDataFuchsia', () {
const Map<int, _ModifierCheck> modifierTests = <int, _ModifierCheck>{
RawKeyEventDataFuchsia.modifierAlt: _ModifierCheck(ModifierKey.altModifier, KeyboardSide.any),
RawKeyEventDataFuchsia.modifierLeftAlt: _ModifierCheck(ModifierKey.altModifier, KeyboardSide.left),
RawKeyEventDataFuchsia.modifierRightAlt: _ModifierCheck(ModifierKey.altModifier, KeyboardSide.right),
RawKeyEventDataFuchsia.modifierShift: _ModifierCheck(ModifierKey.shiftModifier, KeyboardSide.any),
RawKeyEventDataFuchsia.modifierLeftShift: _ModifierCheck(ModifierKey.shiftModifier, KeyboardSide.left),
RawKeyEventDataFuchsia.modifierRightShift: _ModifierCheck(ModifierKey.shiftModifier, KeyboardSide.right),
RawKeyEventDataFuchsia.modifierControl: _ModifierCheck(ModifierKey.controlModifier, KeyboardSide.any),
RawKeyEventDataFuchsia.modifierLeftControl: _ModifierCheck(ModifierKey.controlModifier, KeyboardSide.left),
RawKeyEventDataFuchsia.modifierRightControl: _ModifierCheck(ModifierKey.controlModifier, KeyboardSide.right),
RawKeyEventDataFuchsia.modifierMeta: _ModifierCheck(ModifierKey.metaModifier, KeyboardSide.any),
RawKeyEventDataFuchsia.modifierLeftMeta: _ModifierCheck(ModifierKey.metaModifier, KeyboardSide.left),
RawKeyEventDataFuchsia.modifierRightMeta: _ModifierCheck(ModifierKey.metaModifier, KeyboardSide.right),
RawKeyEventDataFuchsia.modifierCapsLock: _ModifierCheck(ModifierKey.capsLockModifier, KeyboardSide.any),
};
test('modifier keys are recognized individually', () {
for (final int modifier in modifierTests.keys) {
final RawKeyEvent event = RawKeyEvent.fromMessage(<String, dynamic>{
'type': 'keydown',
'keymap': 'fuchsia',
'hidUsage': 0x04,
'codePoint': 0x64,
'modifiers': modifier,
});
final RawKeyEventDataFuchsia data = event.data as RawKeyEventDataFuchsia;
for (final ModifierKey key in ModifierKey.values) {
if (modifierTests[modifier]!.key == key) {
expect(
data.isModifierPressed(key, side: modifierTests[modifier]!.side),
isTrue,
reason: "$key should be pressed with metaState $modifier, but isn't.",
);
} else {
expect(
data.isModifierPressed(key, side: modifierTests[modifier]!.side),
isFalse,
reason: '$key should not be pressed with metaState $modifier.',
);
}
}
}
});
test('modifier keys are recognized when combined', () {
for (final int modifier in modifierTests.keys) {
if (modifier == RawKeyEventDataFuchsia.modifierCapsLock) {
// No need to combine caps lock key with itself.
continue;
}
final RawKeyEvent event = RawKeyEvent.fromMessage(<String, dynamic>{
'type': 'keydown',
'keymap': 'fuchsia',
'hidUsage': 0x04,
'codePoint': 0x64,
'modifiers': modifier | RawKeyEventDataFuchsia.modifierCapsLock,
});
final RawKeyEventDataFuchsia data = event.data as RawKeyEventDataFuchsia;
for (final ModifierKey key in ModifierKey.values) {
if (modifierTests[modifier]!.key == key || key == ModifierKey.capsLockModifier) {
expect(
data.isModifierPressed(key, side: modifierTests[modifier]!.side),
isTrue,
reason: '$key should be pressed with metaState $modifier '
"and additional key ${RawKeyEventDataFuchsia.modifierCapsLock}, but isn't.",
);
} else {
expect(
data.isModifierPressed(key, side: modifierTests[modifier]!.side),
isFalse,
reason: '$key should not be pressed with metaState $modifier '
'and additional key ${RawKeyEventDataFuchsia.modifierCapsLock}.',
);
}
}
}
});
test('Printable keyboard keys are correctly translated', () {
final RawKeyEvent keyAEvent = RawKeyEvent.fromMessage(<String, dynamic>{
'type': 'keydown',
'keymap': 'fuchsia',
'hidUsage': 0x00070004,
'codePoint': 'a'.codeUnitAt(0),
'character': 'a',
});
final RawKeyEventDataFuchsia data = keyAEvent.data as RawKeyEventDataFuchsia;
expect(data.physicalKey, equals(PhysicalKeyboardKey.keyA));
expect(data.logicalKey, equals(LogicalKeyboardKey.keyA));
expect(data.keyLabel, equals('a'));
});
test('Control keyboard keys are correctly translated', () {
final RawKeyEvent escapeKeyEvent = RawKeyEvent.fromMessage(const <String, dynamic>{
'type': 'keydown',
'keymap': 'fuchsia',
'hidUsage': 0x00070029,
});
final RawKeyEventDataFuchsia data = escapeKeyEvent.data as RawKeyEventDataFuchsia;
expect(data.physicalKey, equals(PhysicalKeyboardKey.escape));
expect(data.logicalKey, equals(LogicalKeyboardKey.escape));
expect(data.keyLabel, isEmpty);
}, skip: isBrowser); // https://github.com/flutter/flutter/issues/35347
test('Modifier keyboard keys are correctly translated', () {
final RawKeyEvent shiftLeftKeyEvent = RawKeyEvent.fromMessage(const <String, dynamic>{
'type': 'keydown',
'keymap': 'fuchsia',
'hidUsage': 0x000700e1,
});
final RawKeyEventDataFuchsia data = shiftLeftKeyEvent.data as RawKeyEventDataFuchsia;
expect(data.physicalKey, equals(PhysicalKeyboardKey.shiftLeft));
expect(data.logicalKey, equals(LogicalKeyboardKey.shiftLeft));
expect(data.keyLabel, isEmpty);
}, skip: isBrowser); // https://github.com/flutter/flutter/issues/35347
});
group('RawKeyEventDataMacOs', () {
const Map<int, _ModifierCheck> modifierTests = <int, _ModifierCheck>{
RawKeyEventDataMacOs.modifierOption | RawKeyEventDataMacOs.modifierLeftOption: _ModifierCheck(ModifierKey.altModifier, KeyboardSide.left),
RawKeyEventDataMacOs.modifierOption | RawKeyEventDataMacOs.modifierRightOption: _ModifierCheck(ModifierKey.altModifier, KeyboardSide.right),
RawKeyEventDataMacOs.modifierShift | RawKeyEventDataMacOs.modifierLeftShift: _ModifierCheck(ModifierKey.shiftModifier, KeyboardSide.left),
RawKeyEventDataMacOs.modifierShift | RawKeyEventDataMacOs.modifierRightShift: _ModifierCheck(ModifierKey.shiftModifier, KeyboardSide.right),
RawKeyEventDataMacOs.modifierControl | RawKeyEventDataMacOs.modifierLeftControl: _ModifierCheck(ModifierKey.controlModifier, KeyboardSide.left),
RawKeyEventDataMacOs.modifierControl | RawKeyEventDataMacOs.modifierRightControl: _ModifierCheck(ModifierKey.controlModifier, KeyboardSide.right),
RawKeyEventDataMacOs.modifierCommand | RawKeyEventDataMacOs.modifierLeftCommand: _ModifierCheck(ModifierKey.metaModifier, KeyboardSide.left),
RawKeyEventDataMacOs.modifierCommand | RawKeyEventDataMacOs.modifierRightCommand: _ModifierCheck(ModifierKey.metaModifier, KeyboardSide.right),
RawKeyEventDataMacOs.modifierOption: _ModifierCheck(ModifierKey.altModifier, KeyboardSide.all),
RawKeyEventDataMacOs.modifierShift: _ModifierCheck(ModifierKey.shiftModifier, KeyboardSide.all),
RawKeyEventDataMacOs.modifierControl: _ModifierCheck(ModifierKey.controlModifier, KeyboardSide.all),
RawKeyEventDataMacOs.modifierCommand: _ModifierCheck(ModifierKey.metaModifier, KeyboardSide.all),
RawKeyEventDataMacOs.modifierCapsLock: _ModifierCheck(ModifierKey.capsLockModifier, KeyboardSide.all),
};
test('modifier keys are recognized individually', () {
for (final int modifier in modifierTests.keys) {
final RawKeyEvent event = RawKeyEvent.fromMessage(<String, dynamic>{
'type': 'keydown',
'keymap': 'macos',
'keyCode': 0x04,
'characters': 'a',
'charactersIgnoringModifiers': 'a',
'modifiers': modifier,
});
final RawKeyEventDataMacOs data = event.data as RawKeyEventDataMacOs;
for (final ModifierKey key in ModifierKey.values) {
if (modifierTests[modifier]!.key == key) {
expect(
data.isModifierPressed(key, side: modifierTests[modifier]!.side),
isTrue,
reason: "$key should be pressed with metaState $modifier, but isn't.",
);
expect(data.getModifierSide(key), equals(modifierTests[modifier]!.side));
} else {
expect(
data.isModifierPressed(key, side: modifierTests[modifier]!.side),
isFalse,
reason: '$key should not be pressed with metaState $modifier.',
);
}
}
}
});
test('modifier keys are recognized when combined', () {
for (final int modifier in modifierTests.keys) {
if (modifier == RawKeyEventDataMacOs.modifierCapsLock) {
// No need to combine caps lock key with itself.
continue;
}
final RawKeyEvent event = RawKeyEvent.fromMessage(<String, dynamic>{
'type': 'keydown',
'keymap': 'macos',
'keyCode': 0x04,
'plainCodePoint': 0x64,
'characters': 'a',
'charactersIgnoringModifiers': 'a',
'modifiers': modifier | RawKeyEventDataMacOs.modifierCapsLock,
});
final RawKeyEventDataMacOs data = event.data as RawKeyEventDataMacOs;
for (final ModifierKey key in ModifierKey.values) {
if (modifierTests[modifier]!.key == key || key == ModifierKey.capsLockModifier) {
expect(
data.isModifierPressed(key, side: modifierTests[modifier]!.side),
isTrue,
reason: '$key should be pressed with metaState $modifier '
"and additional key ${RawKeyEventDataMacOs.modifierCapsLock}, but isn't.",
);
if (key != ModifierKey.capsLockModifier) {
expect(data.getModifierSide(key), equals(modifierTests[modifier]!.side));
} else {
expect(data.getModifierSide(key), equals(KeyboardSide.all));
}
} else {
expect(
data.isModifierPressed(key, side: modifierTests[modifier]!.side),
isFalse,
reason: '$key should not be pressed with metaState $modifier '
'and additional key ${RawKeyEventDataMacOs.modifierCapsLock}.',
);
}
}
}
});
test('Printable keyboard keys are correctly translated', () {
const String unmodifiedCharacter = 'a';
final RawKeyEvent keyAEvent = RawKeyEvent.fromMessage(const <String, dynamic>{
'type': 'keydown',
'keymap': 'macos',
'keyCode': 0x00000000,
'characters': 'a',
'charactersIgnoringModifiers': unmodifiedCharacter,
'modifiers': 0x0,
});
final RawKeyEventDataMacOs data = keyAEvent.data as RawKeyEventDataMacOs;
expect(data.physicalKey, equals(PhysicalKeyboardKey.keyA));
expect(data.logicalKey, equals(LogicalKeyboardKey.keyA));
expect(data.keyLabel, equals('a'));
});
test('Control keyboard keys are correctly translated', () {
final RawKeyEvent escapeKeyEvent = RawKeyEvent.fromMessage(const <String, dynamic>{
'type': 'keydown',
'keymap': 'macos',
'keyCode': 0x00000035,
'characters': '',
'charactersIgnoringModifiers': '',
'modifiers': 0x0,
});
final RawKeyEventDataMacOs data = escapeKeyEvent.data as RawKeyEventDataMacOs;
expect(data.physicalKey, equals(PhysicalKeyboardKey.escape));
expect(data.logicalKey, equals(LogicalKeyboardKey.escape));
expect(data.keyLabel, isEmpty);
}, skip: isBrowser); // https://github.com/flutter/flutter/issues/35347
test('Modifier keyboard keys are correctly translated', () {
final RawKeyEvent shiftLeftKeyEvent = RawKeyEvent.fromMessage(const <String, dynamic>{
'type': 'keydown',
'keymap': 'macos',
'keyCode': 0x00000038,
'characters': '',
'charactersIgnoringModifiers': '',
'modifiers': RawKeyEventDataMacOs.modifierLeftShift,
});
final RawKeyEventDataMacOs data = shiftLeftKeyEvent.data as RawKeyEventDataMacOs;
expect(data.physicalKey, equals(PhysicalKeyboardKey.shiftLeft));
expect(data.logicalKey, equals(LogicalKeyboardKey.shiftLeft));
expect(data.keyLabel, isEmpty);
}, skip: isBrowser); // https://github.com/flutter/flutter/issues/35347
test('Unprintable keyboard keys are correctly translated', () {
final RawKeyEvent leftArrowKey = RawKeyEvent.fromMessage(const <String, dynamic>{
'type': 'keydown',
'keymap': 'macos',
'keyCode': 0x0000007B,
'characters': '',
'charactersIgnoringModifiers': '', // NSLeftArrowFunctionKey = 0xF702
'modifiers': RawKeyEventDataMacOs.modifierFunction,
});
final RawKeyEventDataMacOs data = leftArrowKey.data as RawKeyEventDataMacOs;
expect(data.physicalKey, equals(PhysicalKeyboardKey.arrowLeft));
expect(data.logicalKey, equals(LogicalKeyboardKey.arrowLeft));
expect(data.logicalKey.keyLabel, isEmpty);
}, skip: isBrowser); // https://github.com/flutter/flutter/issues/35347
});
group('RawKeyEventDataWindows', () {
const Map<int, _ModifierCheck> modifierTests = <int, _ModifierCheck>{
RawKeyEventDataWindows.modifierLeftAlt: _ModifierCheck(ModifierKey.altModifier, KeyboardSide.left),
RawKeyEventDataWindows.modifierRightAlt: _ModifierCheck(ModifierKey.altModifier, KeyboardSide.right),
RawKeyEventDataWindows.modifierLeftShift: _ModifierCheck(ModifierKey.shiftModifier, KeyboardSide.left),
RawKeyEventDataWindows.modifierRightShift: _ModifierCheck(ModifierKey.shiftModifier, KeyboardSide.right),
RawKeyEventDataWindows.modifierLeftControl: _ModifierCheck(ModifierKey.controlModifier, KeyboardSide.left),
RawKeyEventDataWindows.modifierRightControl: _ModifierCheck(ModifierKey.controlModifier, KeyboardSide.right),
RawKeyEventDataWindows.modifierLeftMeta: _ModifierCheck(ModifierKey.metaModifier, KeyboardSide.left),
RawKeyEventDataWindows.modifierRightMeta: _ModifierCheck(ModifierKey.metaModifier, KeyboardSide.right),
RawKeyEventDataWindows.modifierShift: _ModifierCheck(ModifierKey.shiftModifier, KeyboardSide.all),
RawKeyEventDataWindows.modifierControl: _ModifierCheck(ModifierKey.controlModifier, KeyboardSide.all),
RawKeyEventDataWindows.modifierAlt: _ModifierCheck(ModifierKey.altModifier, KeyboardSide.all),
RawKeyEventDataWindows.modifierCaps: _ModifierCheck(ModifierKey.capsLockModifier, KeyboardSide.all),
RawKeyEventDataWindows.modifierNumLock: _ModifierCheck(ModifierKey.numLockModifier, KeyboardSide.all),
RawKeyEventDataWindows.modifierScrollLock: _ModifierCheck(ModifierKey.scrollLockModifier, KeyboardSide.all),
};
test('modifier keys are recognized individually', () {
for (final int modifier in modifierTests.keys) {
final RawKeyEvent event = RawKeyEvent.fromMessage(<String, dynamic>{
'type': 'keydown',
'keymap': 'windows',
'keyCode': 0x04,
'characterCodePoint': 0,
'scanCode': 0x04,
'modifiers': modifier,
});
final RawKeyEventDataWindows data = event.data as RawKeyEventDataWindows;
for (final ModifierKey key in ModifierKey.values) {
if (modifierTests[modifier]!.key == key) {
expect(
data.isModifierPressed(key, side: modifierTests[modifier]!.side),
isTrue,
reason: "$key should be pressed with modifier $modifier, but isn't.",
);
expect(data.getModifierSide(key), equals(modifierTests[modifier]!.side));
} else {
expect(
data.isModifierPressed(key, side: modifierTests[modifier]!.side),
isFalse,
reason: '$key should not be pressed with metaState $modifier.',
);
}
}
}
});
test('modifier keys are recognized when combined', () {
for (final int modifier in modifierTests.keys) {
if (modifier == RawKeyEventDataWindows.modifierCaps) {
// No need to combine caps lock key with itself.
continue;
}
final RawKeyEvent event = RawKeyEvent.fromMessage(<String, dynamic>{
'type': 'keydown',
'keymap': 'windows',
'keyCode': 0x04,
'characterCodePoint': 0,
'scanCode': 0x04,
'modifiers': modifier | RawKeyEventDataWindows.modifierCaps,
});
final RawKeyEventDataWindows data = event.data as RawKeyEventDataWindows;
for (final ModifierKey key in ModifierKey.values) {
if (modifierTests[modifier]!.key == key || key == ModifierKey.capsLockModifier) {
expect(
data.isModifierPressed(key, side: modifierTests[modifier]!.side),
isTrue,
reason: '$key should be pressed with metaState $modifier '
"and additional key ${RawKeyEventDataWindows.modifierCaps}, but isn't.",
);
if (key != ModifierKey.capsLockModifier) {
expect(data.getModifierSide(key), equals(modifierTests[modifier]!.side));
} else {
expect(data.getModifierSide(key), equals(KeyboardSide.all));
}
} else {
expect(
data.isModifierPressed(key, side: modifierTests[modifier]!.side),
isFalse,
reason: '$key should not be pressed with metaState $modifier '
'and additional key ${RawKeyEventDataWindows.modifierCaps}.',
);
}
}
}
});
test('Printable keyboard keys are correctly translated', () {
const int unmodifiedCharacter = 97; // ASCII value for 'a'.
final RawKeyEvent keyAEvent = RawKeyEvent.fromMessage(const <String, dynamic>{
'type': 'keydown',
'keymap': 'windows',
'keyCode': 0x00000000,
'characterCodePoint': unmodifiedCharacter,
'scanCode': 0x0000001e,
'modifiers': 0x0,
});
final RawKeyEventDataWindows data = keyAEvent.data as RawKeyEventDataWindows;
expect(data.physicalKey, equals(PhysicalKeyboardKey.keyA));
expect(data.logicalKey, equals(LogicalKeyboardKey.keyA));
expect(data.keyLabel, equals('a'));
});
test('Control keyboard keys are correctly translated', () {
final RawKeyEvent escapeKeyEvent = RawKeyEvent.fromMessage(const <String, dynamic>{
'type': 'keydown',
'keymap': 'windows',
'keyCode': 27, // keycode for escape key
'scanCode': 0x00000001, // scanCode for escape key
'characterCodePoint': 0,
'modifiers': 0x0,
});
final RawKeyEventDataWindows data = escapeKeyEvent.data as RawKeyEventDataWindows;
expect(data.physicalKey, equals(PhysicalKeyboardKey.escape));
expect(data.logicalKey, equals(LogicalKeyboardKey.escape));
expect(data.keyLabel, isEmpty);
});
test('Modifier keyboard keys are correctly translated', () {
final RawKeyEvent shiftLeftKeyEvent = RawKeyEvent.fromMessage(const <String, dynamic>{
'type': 'keydown',
'keymap': 'windows',
'keyCode': 160, // keyCode for left shift.
'scanCode': 0x0000002a, // scanCode for left shift.
'characterCodePoint': 0,
'modifiers': RawKeyEventDataWindows.modifierLeftShift,
});
final RawKeyEventDataWindows data = shiftLeftKeyEvent.data as RawKeyEventDataWindows;
expect(data.physicalKey, equals(PhysicalKeyboardKey.shiftLeft));
expect(data.logicalKey, equals(LogicalKeyboardKey.shiftLeft));
expect(data.keyLabel, isEmpty);
});
test('Unprintable keyboard keys are correctly translated', () {
final RawKeyEvent leftArrowKey = RawKeyEvent.fromMessage(const <String, dynamic>{
'type': 'keydown',
'keymap': 'windows',
'keyCode': 37, // keyCode for left arrow.
'scanCode': 0x0000e04b, // scanCode for left arrow.
'characterCodePoint': 0,
'modifiers': 0,
});
final RawKeyEventDataWindows data = leftArrowKey.data as RawKeyEventDataWindows;
expect(data.physicalKey, equals(PhysicalKeyboardKey.arrowLeft));
expect(data.logicalKey, equals(LogicalKeyboardKey.arrowLeft));
expect(data.logicalKey.keyLabel, isEmpty);
});
});
group('RawKeyEventDataLinux-GFLW', () {
const Map<int, _ModifierCheck> modifierTests = <int, _ModifierCheck>{
GLFWKeyHelper.modifierAlt: _ModifierCheck(ModifierKey.altModifier, KeyboardSide.all),
GLFWKeyHelper.modifierShift: _ModifierCheck(ModifierKey.shiftModifier, KeyboardSide.all),
GLFWKeyHelper.modifierControl: _ModifierCheck(ModifierKey.controlModifier, KeyboardSide.all),
GLFWKeyHelper.modifierMeta: _ModifierCheck(ModifierKey.metaModifier, KeyboardSide.all),
GLFWKeyHelper.modifierNumericPad: _ModifierCheck(ModifierKey.numLockModifier, KeyboardSide.all),
GLFWKeyHelper.modifierCapsLock: _ModifierCheck(ModifierKey.capsLockModifier, KeyboardSide.all),
};
// How modifiers are interpreted depends upon the keyCode for GLFW.
int keyCodeForModifier(int modifier, {required bool isLeft}) {
switch (modifier) {
case GLFWKeyHelper.modifierAlt:
return isLeft ? 342 : 346;
case GLFWKeyHelper.modifierShift:
return isLeft ? 340 : 344;
case GLFWKeyHelper.modifierControl:
return isLeft ? 341 : 345;
case GLFWKeyHelper.modifierMeta:
return isLeft ? 343 : 347;
case GLFWKeyHelper.modifierNumericPad:
return 282;
case GLFWKeyHelper.modifierCapsLock:
return 280;
default:
return 65; // keyA
}
}
test('modifier keys are recognized individually', () {
for (final int modifier in modifierTests.keys) {
for (final bool isDown in <bool>[true, false]) {
for (final bool isLeft in <bool>[true, false]) {
final RawKeyEvent event = RawKeyEvent.fromMessage(<String, dynamic>{
'type': isDown ? 'keydown' : 'keyup',
'keymap': 'linux',
'toolkit': 'glfw',
'keyCode': keyCodeForModifier(modifier, isLeft: isLeft),
'scanCode': 0x00000026,
'unicodeScalarValues': 97,
// GLFW modifiers don't include the current key event.
'modifiers': isDown ? 0 : modifier,
});
final RawKeyEventDataLinux data = event.data as RawKeyEventDataLinux;
for (final ModifierKey key in ModifierKey.values) {
if (modifierTests[modifier]!.key == key) {
expect(
data.isModifierPressed(key, side: modifierTests[modifier]!.side),
isDown ? isTrue : isFalse,
reason: "${isLeft ? 'left' : 'right'} $key ${isDown ? 'should' : 'should not'} be pressed with metaState $modifier, when key is ${isDown ? 'down' : 'up'}, but isn't.",
);
expect(data.getModifierSide(key), equals(modifierTests[modifier]!.side));
} else {
expect(
data.isModifierPressed(key, side: modifierTests[modifier]!.side),
isFalse,
reason: "${isLeft ? 'left' : 'right'} $key should not be pressed with metaState $modifier, wwhen key is ${isDown ? 'down' : 'up'}, but is.",
);
}
}
}
}
}
});
test('modifier keys are recognized when combined', () {
for (final int modifier in modifierTests.keys) {
if (modifier == GLFWKeyHelper.modifierControl) {
// No need to combine CTRL key with itself.
continue;
}
final RawKeyEvent event = RawKeyEvent.fromMessage(<String, dynamic>{
'type': 'keydown',
'keymap': 'linux',
'toolkit': 'glfw',
'keyCode': 65,
'scanCode': 0x00000026,
'unicodeScalarValues': 97,
'modifiers': modifier | GLFWKeyHelper.modifierControl,
});
final RawKeyEventDataLinux data = event.data as RawKeyEventDataLinux;
for (final ModifierKey key in ModifierKey.values) {
if (modifierTests[modifier]!.key == key || key == ModifierKey.controlModifier) {
expect(
data.isModifierPressed(key, side: modifierTests[modifier]!.side),
isTrue,
reason: '$key should be pressed with metaState $modifier '
"and additional key ${GLFWKeyHelper.modifierControl}, but isn't.",
);
if (key != ModifierKey.controlModifier) {
expect(data.getModifierSide(key), equals(modifierTests[modifier]!.side));
} else {
expect(data.getModifierSide(key), equals(KeyboardSide.all));
}
} else {
expect(
data.isModifierPressed(key, side: modifierTests[modifier]!.side),
isFalse,
reason: '$key should not be pressed with metaState $modifier '
'and additional key ${GLFWKeyHelper.modifierControl}.',
);
}
}
}
});
test('Printable keyboard keys are correctly translated', () {
final RawKeyEvent keyAEvent = RawKeyEvent.fromMessage(const <String, dynamic>{
'type': 'keydown',
'keymap': 'linux',
'toolkit': 'glfw',
'keyCode': 65,
'scanCode': 0x00000026,
'unicodeScalarValues': 113,
'modifiers': 0x0,
});
final RawKeyEventDataLinux data = keyAEvent.data as RawKeyEventDataLinux;
expect(data.physicalKey, equals(PhysicalKeyboardKey.keyA));
expect(data.logicalKey, equals(LogicalKeyboardKey.keyQ));
expect(data.keyLabel, equals('q'));
});
test('Code points with two Unicode scalar values are allowed', () {
final RawKeyEvent keyAEvent = RawKeyEvent.fromMessage(const <String, dynamic>{
'type': 'keydown',
'keymap': 'linux',
'toolkit': 'glfw',
'keyCode': 65,
'scanCode': 0x00000026,
'unicodeScalarValues': 0x10FFFF,
'modifiers': 0x0,
});
final RawKeyEventDataLinux data = keyAEvent.data as RawKeyEventDataLinux;
expect(data.physicalKey, equals(PhysicalKeyboardKey.keyA));
expect(data.logicalKey.keyId, equals(0x10FFFF));
expect(data.keyLabel, equals('􏿿'));
});
test('Code points with more than three Unicode scalar values are not allowed', () {
// |keyCode| and |scanCode| are arbitrary values. This test should fail due to an invalid |unicodeScalarValues|.
void _createFailingKey() {
RawKeyEvent.fromMessage(const <String, dynamic>{
'type': 'keydown',
'keymap': 'linux',
'toolkit': 'glfw',
'keyCode': 65,
'scanCode': 0x00000026,
'unicodeScalarValues': 0x1F00000000,
'modifiers': 0x0,
});
}
expect(() => _createFailingKey(), throwsAssertionError);
}, skip: isBrowser); // https://github.com/flutter/flutter/issues/61039
test('Control keyboard keys are correctly translated', () {
final RawKeyEvent escapeKeyEvent = RawKeyEvent.fromMessage(const <String, dynamic>{
'type': 'keydown',
'keymap': 'linux',
'toolkit': 'glfw',
'keyCode': 256,
'scanCode': 0x00000009,
'unicodeScalarValues': 0,
'modifiers': 0x0,
});
final RawKeyEventDataLinux data = escapeKeyEvent.data as RawKeyEventDataLinux;
expect(data.physicalKey, equals(PhysicalKeyboardKey.escape));
expect(data.logicalKey, equals(LogicalKeyboardKey.escape));
expect(data.keyLabel, isEmpty);
});
test('Modifier keyboard keys are correctly translated', () {
final RawKeyEvent shiftLeftKeyEvent = RawKeyEvent.fromMessage(const <String, dynamic>{
'type': 'keydown',
'keymap': 'linux',
'toolkit': 'glfw',
'keyCode': 340,
'scanCode': 0x00000032,
'unicodeScalarValues': 0,
});
final RawKeyEventDataLinux data = shiftLeftKeyEvent.data as RawKeyEventDataLinux;
expect(data.physicalKey, equals(PhysicalKeyboardKey.shiftLeft));
expect(data.logicalKey, equals(LogicalKeyboardKey.shiftLeft));
expect(data.keyLabel, isEmpty);
});
});
group('RawKeyEventDataLinux-GTK', () {
const Map<int, _ModifierCheck> modifierTests = <int, _ModifierCheck>{
GtkKeyHelper.modifierMod1: _ModifierCheck(ModifierKey.altModifier, KeyboardSide.all),
GtkKeyHelper.modifierShift: _ModifierCheck(ModifierKey.shiftModifier, KeyboardSide.all),
GtkKeyHelper.modifierControl: _ModifierCheck(ModifierKey.controlModifier, KeyboardSide.all),
GtkKeyHelper.modifierMeta: _ModifierCheck(ModifierKey.metaModifier, KeyboardSide.all),
GtkKeyHelper.modifierMod2: _ModifierCheck(ModifierKey.numLockModifier, KeyboardSide.all),
GtkKeyHelper.modifierCapsLock: _ModifierCheck(ModifierKey.capsLockModifier, KeyboardSide.all),
};
// How modifiers are interpreted depends upon the keyCode for GTK.
int keyCodeForModifier(int modifier, {required bool isLeft}) {
switch (modifier) {
case GtkKeyHelper.modifierMod1:
return isLeft ? 65513 : 65513;
case GtkKeyHelper.modifierShift:
return isLeft ? 65505 : 65506;
case GtkKeyHelper.modifierControl:
return isLeft ? 65507 : 65508;
case GtkKeyHelper.modifierMeta:
return isLeft ? 65511 : 65512;
case GtkKeyHelper.modifierMod2:
return 65407;
case GtkKeyHelper.modifierCapsLock:
return 65509;
default:
return 65; // keyA
}
}
test('modifier keys are recognized individually', () {
for (final int modifier in modifierTests.keys) {
for (final bool isDown in <bool>[true, false]) {
for (final bool isLeft in <bool>[true, false]) {
final RawKeyEvent event = RawKeyEvent.fromMessage(<String, dynamic>{
'type': isDown ? 'keydown' : 'keyup',
'keymap': 'linux',
'toolkit': 'gtk',
'keyCode': keyCodeForModifier(modifier, isLeft: isLeft),
'scanCode': 0x00000026,
'unicodeScalarValues': 97,
// GTK modifiers don't include the current key event.
'modifiers': isDown ? 0 : modifier,
});
final RawKeyEventDataLinux data = event.data as RawKeyEventDataLinux;
for (final ModifierKey key in ModifierKey.values) {
if (modifierTests[modifier]!.key == key) {
expect(
data.isModifierPressed(key, side: modifierTests[modifier]!.side),
isDown ? isTrue : isFalse,
reason: "${isLeft ? 'left' : 'right'} $key ${isDown ? 'should' : 'should not'} be pressed with metaState $modifier, when key is ${isDown ? 'down' : 'up'}, but isn't.",
);
expect(data.getModifierSide(key), equals(modifierTests[modifier]!.side));
} else {
expect(
data.isModifierPressed(key, side: modifierTests[modifier]!.side),
isFalse,
reason: "${isLeft ? 'left' : 'right'} $key should not be pressed with metaState $modifier, wwhen key is ${isDown ? 'down' : 'up'}, but is.",
);
}
}
}
}
}
});
test('modifier keys are recognized when combined', () {
for (final int modifier in modifierTests.keys) {
if (modifier == GtkKeyHelper.modifierControl) {
// No need to combine CTRL key with itself.
continue;
}
final RawKeyEvent event = RawKeyEvent.fromMessage(<String, dynamic>{
'type': 'keydown',
'keymap': 'linux',
'toolkit': 'gtk',
'keyCode': 65,
'scanCode': 0x00000026,
'unicodeScalarValues': 97,
'modifiers': modifier | GtkKeyHelper.modifierControl,
});
final RawKeyEventDataLinux data = event.data as RawKeyEventDataLinux;
for (final ModifierKey key in ModifierKey.values) {
if (modifierTests[modifier]!.key == key || key == ModifierKey.controlModifier) {
expect(
data.isModifierPressed(key, side: modifierTests[modifier]!.side),
isTrue,
reason: '$key should be pressed with metaState $modifier '
"and additional key ${GtkKeyHelper.modifierControl}, but isn't.",
);
if (key != ModifierKey.controlModifier) {
expect(data.getModifierSide(key), equals(modifierTests[modifier]!.side));
} else {
expect(data.getModifierSide(key), equals(KeyboardSide.all));
}
} else {
expect(
data.isModifierPressed(key, side: modifierTests[modifier]!.side),
isFalse,
reason: '$key should not be pressed with metaState $modifier '
'and additional key ${GtkKeyHelper.modifierControl}.',
);
}
}
}
});
test('Printable keyboard keys are correctly translated', () {
final RawKeyEvent keyAEvent = RawKeyEvent.fromMessage(const <String, dynamic>{
'type': 'keydown',
'keymap': 'linux',
'toolkit': 'gtk',
'keyCode': 65,
'scanCode': 0x00000026,
'unicodeScalarValues': 113,
'modifiers': 0x0,
});
final RawKeyEventDataLinux data = keyAEvent.data as RawKeyEventDataLinux;
expect(data.physicalKey, equals(PhysicalKeyboardKey.keyA));
expect(data.logicalKey, equals(LogicalKeyboardKey.keyQ));
expect(data.keyLabel, equals('q'));
});
test('Code points with two Unicode scalar values are allowed', () {
final RawKeyEvent keyAEvent = RawKeyEvent.fromMessage(const <String, dynamic>{
'type': 'keydown',
'keymap': 'linux',
'toolkit': 'gtk',
'keyCode': 65,
'scanCode': 0x00000026,
'unicodeScalarValues': 0x10FFFF,
'modifiers': 0x0,
});
final RawKeyEventDataLinux data = keyAEvent.data as RawKeyEventDataLinux;
expect(data.physicalKey, equals(PhysicalKeyboardKey.keyA));
expect(data.logicalKey.keyId, equals(0x10FFFF));
expect(data.keyLabel, equals('􏿿'));
});
test('Code points with more than three Unicode scalar values are not allowed', () {
// |keyCode| and |scanCode| are arbitrary values. This test should fail due to an invalid |unicodeScalarValues|.
void _createFailingKey() {
RawKeyEvent.fromMessage(const <String, dynamic>{
'type': 'keydown',
'keymap': 'linux',
'toolkit': 'gtk',
'keyCode': 65,
'scanCode': 0x00000026,
'unicodeScalarValues': 0x1F00000000,
'modifiers': 0x0,
});
}
expect(() => _createFailingKey(), throwsAssertionError);
}, skip: isBrowser); // https://github.com/flutter/flutter/issues/61039
test('Control keyboard keys are correctly translated', () {
final RawKeyEvent escapeKeyEvent = RawKeyEvent.fromMessage(const <String, dynamic>{
'type': 'keydown',
'keymap': 'linux',
'toolkit': 'gtk',
'keyCode': 65307,
'scanCode': 0x00000009,
'unicodeScalarValues': 0,
'modifiers': 0x0,
});
final RawKeyEventDataLinux data = escapeKeyEvent.data as RawKeyEventDataLinux;
expect(data.physicalKey, equals(PhysicalKeyboardKey.escape));
expect(data.logicalKey, equals(LogicalKeyboardKey.escape));
expect(data.keyLabel, isEmpty);
});
test('Modifier keyboard keys are correctly translated', () {
final RawKeyEvent shiftLeftKeyEvent = RawKeyEvent.fromMessage(const <String, dynamic>{
'type': 'keydown',
'keymap': 'linux',
'toolkit': 'gtk',
'keyCode': 65505,
'scanCode': 0x00000032,
'unicodeScalarValues': 0,
});
final RawKeyEventDataLinux data = shiftLeftKeyEvent.data as RawKeyEventDataLinux;
expect(data.physicalKey, equals(PhysicalKeyboardKey.shiftLeft));
expect(data.logicalKey, equals(LogicalKeyboardKey.shiftLeft));
expect(data.keyLabel, isEmpty);
});
});
group('RawKeyEventDataWeb', () {
const Map<int, ModifierKey> modifierTests = <int, ModifierKey>{
RawKeyEventDataWeb.modifierAlt: ModifierKey.altModifier,
RawKeyEventDataWeb.modifierShift: ModifierKey.shiftModifier,
RawKeyEventDataWeb.modifierControl: ModifierKey.controlModifier,
RawKeyEventDataWeb.modifierMeta: ModifierKey.metaModifier,
RawKeyEventDataWeb.modifierCapsLock: ModifierKey.capsLockModifier,
RawKeyEventDataWeb.modifierNumLock: ModifierKey.numLockModifier,
RawKeyEventDataWeb.modifierScrollLock: ModifierKey.scrollLockModifier,
};
test('modifier keys are recognized individually', () {
for (final int modifier in modifierTests.keys) {
final RawKeyEvent event = RawKeyEvent.fromMessage(<String, dynamic>{
'type': 'keydown',
'keymap': 'web',
'code': 'RandomCode',
'metaState': modifier,
});
final RawKeyEventDataWeb data = event.data as RawKeyEventDataWeb;
for (final ModifierKey key in ModifierKey.values) {
if (modifierTests[modifier] == key) {
expect(
data.isModifierPressed(key),
isTrue,
reason: "$key should be pressed with metaState $modifier, but isn't.",
);
} else {
expect(
data.isModifierPressed(key),
isFalse,
reason: '$key should not be pressed with metaState $modifier.',
);
}
}
}
});
test('modifier keys are recognized when combined', () {
for (final int modifier in modifierTests.keys) {
if (modifier == RawKeyEventDataWeb.modifierMeta) {
// No need to combine meta key with itself.
continue;
}
final RawKeyEvent event = RawKeyEvent.fromMessage(<String, dynamic>{
'type': 'keydown',
'keymap': 'web',
'code': 'RandomCode',
'metaState': modifier | RawKeyEventDataWeb.modifierMeta,
});
final RawKeyEventDataWeb data = event.data as RawKeyEventDataWeb;
for (final ModifierKey key in ModifierKey.values) {
if (modifierTests[modifier] == key || key == ModifierKey.metaModifier) {
expect(
data.isModifierPressed(key),
isTrue,
reason: '$key should be pressed with metaState $modifier '
"and additional key ${RawKeyEventDataWeb.modifierMeta}, but isn't.",
);
} else {
expect(
data.isModifierPressed(key),
isFalse,
reason: '$key should not be pressed with metaState $modifier '
'and additional key ${RawKeyEventDataWeb.modifierMeta}.',
);
}
}
}
});
test('Printable keyboard keys are correctly translated', () {
final RawKeyEvent keyAEvent = RawKeyEvent.fromMessage(const <String, dynamic>{
'type': 'keydown',
'keymap': 'web',
'code': 'KeyA',
'key': 'a',
'metaState': 0x0,
});
final RawKeyEventDataWeb data = keyAEvent.data as RawKeyEventDataWeb;
expect(data.physicalKey, equals(PhysicalKeyboardKey.keyA));
expect(data.logicalKey, equals(LogicalKeyboardKey.keyA));
expect(data.keyLabel, equals('a'));
});
test('Control keyboard keys are correctly translated', () {
final RawKeyEvent escapeKeyEvent = RawKeyEvent.fromMessage(const <String, dynamic>{
'type': 'keydown',
'keymap': 'web',
'code': 'Escape',
'metaState': 0x0,
});
final RawKeyEventDataWeb data = escapeKeyEvent.data as RawKeyEventDataWeb;
expect(data.physicalKey, equals(PhysicalKeyboardKey.escape));
expect(data.logicalKey, equals(LogicalKeyboardKey.escape));
expect(data.keyLabel, isEmpty);
});
test('Modifier keyboard keys are correctly translated', () {
final RawKeyEvent shiftKeyEvent = RawKeyEvent.fromMessage(const <String, dynamic>{
'type': 'keydown',
'keymap': 'web',
'code': 'ShiftLeft',
'metaState': RawKeyEventDataWeb.modifierShift,
});
final RawKeyEventDataWeb data = shiftKeyEvent.data as RawKeyEventDataWeb;
expect(data.physicalKey, equals(PhysicalKeyboardKey.shiftLeft));
expect(data.logicalKey, equals(LogicalKeyboardKey.shiftLeft));
expect(data.keyLabel, isEmpty);
});
test('Arrow keys from a keyboard give correct physical key mappings', () {
final RawKeyEvent arrowKeyDown = RawKeyEvent.fromMessage(const <String, dynamic>{
'type': 'keydown',
'keymap': 'web',
'code': 'ArrowDown',
'metaState': 0x0,
});
final RawKeyEventDataWeb data = arrowKeyDown.data as RawKeyEventDataWeb;
expect(data.physicalKey, equals(PhysicalKeyboardKey.arrowDown));
expect(data.logicalKey, equals(LogicalKeyboardKey.arrowDown));
expect(data.keyLabel, isEmpty);
});
});
}