diff --git a/packages/flutter/lib/services.dart b/packages/flutter/lib/services.dart index 84e9b07f384..9ba05e07175 100644 --- a/packages/flutter/lib/services.dart +++ b/packages/flutter/lib/services.dart @@ -17,6 +17,7 @@ export 'src/services/clipboard.dart'; export 'src/services/font_loader.dart'; export 'src/services/haptic_feedback.dart'; export 'src/services/keyboard_key.dart'; +export 'src/services/keyboard_maps.dart'; export 'src/services/message_codec.dart'; export 'src/services/message_codecs.dart'; export 'src/services/platform_channel.dart'; diff --git a/packages/flutter/lib/src/services/raw_keyboard.dart b/packages/flutter/lib/src/services/raw_keyboard.dart index 8427a69cb50..b04712ff874 100644 --- a/packages/flutter/lib/src/services/raw_keyboard.dart +++ b/packages/flutter/lib/src/services/raw_keyboard.dart @@ -512,4 +512,12 @@ class RawKeyboard { Set get keysPressed { return _keysPressed.toSet(); } + + /// Clears the list of keys returned from [keysPressed]. + /// + /// This is used by the testing framework to make sure tests are hermetic. + @visibleForTesting + void clearKeysPressed() { + _keysPressed.clear(); + } } diff --git a/packages/flutter/test/material/text_field_test.dart b/packages/flutter/test/material/text_field_test.dart index 59ac895b2fb..a3a86ba137a 100644 --- a/packages/flutter/test/material/text_field_test.dart +++ b/packages/flutter/test/material/text_field_test.dart @@ -3531,30 +3531,6 @@ void main() { expect(focusNode2.hasPrimaryFocus, isFalse); }); - void sendFakeKeyEvent(Map data) { - ServicesBinding.instance.defaultBinaryMessenger.handlePlatformMessage( - SystemChannels.keyEvent.name, - SystemChannels.keyEvent.codec.encodeMessage(data), - (ByteData data) { }, - ); - } - - void sendKeyEventWithCode(int code, bool down, bool shiftDown, bool ctrlDown) { - - int metaState = shiftDown ? 1 : 0; - if (ctrlDown) - metaState |= 1 << 12; - - sendFakeKeyEvent({ - 'type': down ? 'keydown' : 'keyup', - 'keymap': 'android', - 'keyCode': code, - 'hidUsage': 0x04, - 'codePoint': 0x64, - 'metaState': metaState, - }); - } - group('Keyboard Tests', () { TextEditingController controller; @@ -3595,7 +3571,8 @@ void main() { await tester.tap(find.byType(TextField)); await tester.pumpAndSettle(); - sendKeyEventWithCode(21, true, true, false); // LEFT_ARROW keydown, SHIFT_ON + await tester.sendKeyDownEvent(LogicalKeyboardKey.shift); + await tester.sendKeyDownEvent(LogicalKeyboardKey.arrowLeft); expect(controller.selection.extentOffset - controller.selection.baseOffset, 1); }); @@ -3611,7 +3588,8 @@ void main() { )); await tester.pump(); - sendKeyEventWithCode(22, true, true, false); + await tester.sendKeyDownEvent(LogicalKeyboardKey.shift); + await tester.sendKeyDownEvent(LogicalKeyboardKey.arrowRight); await tester.pumpAndSettle(); expect(controller.selection.extentOffset - controller.selection.baseOffset, 1); }); @@ -3625,8 +3603,9 @@ void main() { await tester.tap(find.byType(TextField)); await tester.pumpAndSettle(); await tester.pumpAndSettle(); - sendKeyEventWithCode(22, true, true, true); // RIGHT_ARROW keydown SHIFT_ON, CONTROL_ON - + await tester.sendKeyDownEvent(LogicalKeyboardKey.control); + await tester.sendKeyDownEvent(LogicalKeyboardKey.shift); + await tester.sendKeyDownEvent(LogicalKeyboardKey.arrowRight); await tester.pumpAndSettle(); expect(controller.selection.extentOffset - controller.selection.baseOffset, 5); @@ -3643,14 +3622,17 @@ void main() { await tester.tap(find.byType(TextField)); await tester.pumpAndSettle(); - sendKeyEventWithCode(19, true, true, false); // UP_ARROW keydown + await tester.sendKeyDownEvent(LogicalKeyboardKey.shift); + await tester.sendKeyDownEvent(LogicalKeyboardKey.arrowUp); await tester.pumpAndSettle(); expect(controller.selection.extentOffset - controller.selection.baseOffset, 11); - sendKeyEventWithCode(19, false, true, false); // UP_ARROW keyup + await tester.sendKeyUpEvent(LogicalKeyboardKey.arrowUp); + await tester.sendKeyUpEvent(LogicalKeyboardKey.shift); await tester.pumpAndSettle(); - sendKeyEventWithCode(20, true, true, false); // DOWN_ARROW keydown + await tester.sendKeyDownEvent(LogicalKeyboardKey.shift); + await tester.sendKeyDownEvent(LogicalKeyboardKey.arrowDown); await tester.pumpAndSettle(); expect(controller.selection.extentOffset - controller.selection.baseOffset, 0); @@ -3666,42 +3648,47 @@ void main() { await tester.pumpAndSettle(); for (int i = 0; i < 5; i += 1) { - sendKeyEventWithCode(22, true, false, false); // RIGHT_ARROW keydown + await tester.sendKeyDownEvent(LogicalKeyboardKey.arrowRight); await tester.pumpAndSettle(); - sendKeyEventWithCode(22, false, false, false); // RIGHT_ARROW keyup + await tester.sendKeyUpEvent(LogicalKeyboardKey.arrowRight); await tester.pumpAndSettle(); } - sendKeyEventWithCode(20, true, true, false); // DOWN_ARROW keydown + await tester.sendKeyDownEvent(LogicalKeyboardKey.shift); + await tester.sendKeyEvent(LogicalKeyboardKey.arrowDown); await tester.pumpAndSettle(); - sendKeyEventWithCode(20, false, true, false); // DOWN_ARROW keyup + await tester.sendKeyUpEvent(LogicalKeyboardKey.shift); await tester.pumpAndSettle(); expect(controller.selection.extentOffset - controller.selection.baseOffset, 12); - sendKeyEventWithCode(20, true, true, false); // DOWN_ARROW keydown + await tester.sendKeyDownEvent(LogicalKeyboardKey.shift); + await tester.sendKeyEvent(LogicalKeyboardKey.arrowDown); await tester.pumpAndSettle(); - sendKeyEventWithCode(20, false, true, false); // DOWN_ARROW keyup + await tester.sendKeyUpEvent(LogicalKeyboardKey.shift); await tester.pumpAndSettle(); expect(controller.selection.extentOffset - controller.selection.baseOffset, 32); - sendKeyEventWithCode(19, true, true, false); // UP_ARROW keydown + await tester.sendKeyDownEvent(LogicalKeyboardKey.shift); + await tester.sendKeyEvent(LogicalKeyboardKey.arrowUp); await tester.pumpAndSettle(); - sendKeyEventWithCode(19, false, true, false); // UP_ARROW keyup + await tester.sendKeyUpEvent(LogicalKeyboardKey.shift); await tester.pumpAndSettle(); expect(controller.selection.extentOffset - controller.selection.baseOffset, 12); - sendKeyEventWithCode(19, true, true, false); // UP_ARROW keydown + await tester.sendKeyDownEvent(LogicalKeyboardKey.shift); + await tester.sendKeyEvent(LogicalKeyboardKey.arrowUp); await tester.pumpAndSettle(); - sendKeyEventWithCode(19, false, true, false); // UP_ARROW keyup + await tester.sendKeyUpEvent(LogicalKeyboardKey.shift); await tester.pumpAndSettle(); expect(controller.selection.extentOffset - controller.selection.baseOffset, 0); - sendKeyEventWithCode(19, true, true, false); // UP_ARROW keydown + await tester.sendKeyDownEvent(LogicalKeyboardKey.shift); + await tester.sendKeyEvent(LogicalKeyboardKey.arrowUp); await tester.pumpAndSettle(); - sendKeyEventWithCode(19, false, true, false); // UP_ARROW keyup + await tester.sendKeyUpEvent(LogicalKeyboardKey.shift); await tester.pumpAndSettle(); expect(controller.selection.extentOffset - controller.selection.baseOffset, 5); @@ -3722,17 +3709,12 @@ void main() { await tester.tap(find.byType(TextField)); await tester.pumpAndSettle(); - sendKeyEventWithCode(21, true, true, false); // LEFT_ARROW keydown, SHIFT_ON + await tester.sendKeyDownEvent(LogicalKeyboardKey.shift); + await tester.sendKeyDownEvent(LogicalKeyboardKey.arrowLeft); expect(controller.selection.extentOffset - controller.selection.baseOffset, 1); }); }); - const int _kXKeyCode = 52; - const int _kCKeyCode = 31; - const int _kVKeyCode = 50; - const int _kAKeyCode = 29; - const int _kDelKeyCode = 112; - testWidgets('Copy paste test', (WidgetTester tester) async { final FocusNode focusNode = FocusNode(); final TextEditingController controller = TextEditingController(); @@ -3774,32 +3756,32 @@ void main() { await tester.pumpAndSettle(); // Select the first 5 characters + await tester.sendKeyDownEvent(LogicalKeyboardKey.shift); for (int i = 0; i < 5; i += 1) { - sendKeyEventWithCode(22, true, true, false); // RIGHT_ARROW keydown shift - await tester.pumpAndSettle(); - sendKeyEventWithCode(22, false, false, false); // RIGHT_ARROW keyup + await tester.sendKeyEvent(LogicalKeyboardKey.arrowRight); await tester.pumpAndSettle(); } + await tester.sendKeyUpEvent(LogicalKeyboardKey.shift); // Copy them - sendKeyEventWithCode(_kCKeyCode, true, false, true); // keydown control + await tester.sendKeyDownEvent(LogicalKeyboardKey.controlRight); + await tester.sendKeyEvent(LogicalKeyboardKey.keyC); await tester.pumpAndSettle(); - sendKeyEventWithCode(_kCKeyCode, false, false, false); // keyup control + await tester.sendKeyUpEvent(LogicalKeyboardKey.controlRight); await tester.pumpAndSettle(); expect(clipboardContent, 'a big'); - sendKeyEventWithCode(22, true, false, false); // RIGHT_ARROW keydown - await tester.pumpAndSettle(); - sendKeyEventWithCode(22, false, false, false); // RIGHT_ARROW keyup + await tester.sendKeyEvent(LogicalKeyboardKey.arrowRight); await tester.pumpAndSettle(); // Paste them - sendKeyEventWithCode(_kVKeyCode, true, false, true); // Control V keydown + await tester.sendKeyDownEvent(LogicalKeyboardKey.controlRight); + await tester.sendKeyDownEvent(LogicalKeyboardKey.keyV); await tester.pumpAndSettle(); await tester.pump(const Duration(milliseconds: 200)); - - sendKeyEventWithCode(_kVKeyCode, false, false, false); // Control V keyup + await tester.sendKeyUpEvent(LogicalKeyboardKey.keyV); + await tester.sendKeyUpEvent(LogicalKeyboardKey.controlRight); await tester.pumpAndSettle(); const String expected = 'a biga big house\njumped over a mouse'; @@ -3847,33 +3829,34 @@ void main() { // Select the first 5 characters for (int i = 0; i < 5; i += 1) { - sendKeyEventWithCode(22, true, true, false); // RIGHT_ARROW keydown shift + await tester.sendKeyDownEvent(LogicalKeyboardKey.shift); + await tester.sendKeyEvent(LogicalKeyboardKey.arrowRight); await tester.pumpAndSettle(); - sendKeyEventWithCode(22, false, false, false); // RIGHT_ARROW keyup + await tester.sendKeyUpEvent(LogicalKeyboardKey.shift); await tester.pumpAndSettle(); } // Cut them - sendKeyEventWithCode(_kXKeyCode, true, false, true); // keydown control X + await tester.sendKeyDownEvent(LogicalKeyboardKey.controlRight); + await tester.sendKeyEvent(LogicalKeyboardKey.keyX); await tester.pumpAndSettle(); - sendKeyEventWithCode(_kXKeyCode, false, false, false); // keyup control X + await tester.sendKeyUpEvent(LogicalKeyboardKey.controlRight); await tester.pumpAndSettle(); expect(clipboardContent, 'a big'); for (int i = 0; i < 5; i += 1) { - sendKeyEventWithCode(22, true, false, false); // RIGHT_ARROW keydown - await tester.pumpAndSettle(); - sendKeyEventWithCode(22, false, false, false); // RIGHT_ARROW keyup + await tester.sendKeyEvent(LogicalKeyboardKey.arrowRight); await tester.pumpAndSettle(); } // Paste them - sendKeyEventWithCode(_kVKeyCode, true, false, true); // Control V keydown + await tester.sendKeyDownEvent(LogicalKeyboardKey.controlRight); + await tester.sendKeyDownEvent(LogicalKeyboardKey.keyV); await tester.pumpAndSettle(); await tester.pump(const Duration(milliseconds: 200)); - - sendKeyEventWithCode(_kVKeyCode, false, false, false); // Control V keyup + await tester.sendKeyUpEvent(LogicalKeyboardKey.keyV); + await tester.sendKeyUpEvent(LogicalKeyboardKey.controlRight); await tester.pumpAndSettle(); const String expected = ' housa bige\njumped over a mouse'; @@ -3911,17 +3894,18 @@ void main() { await tester.pumpAndSettle(); // Select All - sendKeyEventWithCode(_kAKeyCode, true, false, true); // keydown control A + await tester.sendKeyDownEvent(LogicalKeyboardKey.control); + await tester.sendKeyEvent(LogicalKeyboardKey.keyA); await tester.pumpAndSettle(); - sendKeyEventWithCode(_kAKeyCode, false, false, true); // keyup control A + await tester.sendKeyUpEvent(LogicalKeyboardKey.control); await tester.pumpAndSettle(); // Delete them - sendKeyEventWithCode(_kDelKeyCode, true, false, false); // DEL keydown + await tester.sendKeyDownEvent(LogicalKeyboardKey.delete); await tester.pumpAndSettle(); await tester.pump(const Duration(milliseconds: 200)); - sendKeyEventWithCode(_kDelKeyCode, false, false, false); // DEL keyup + await tester.sendKeyUpEvent(LogicalKeyboardKey.delete); await tester.pumpAndSettle(); const String expected = ''; @@ -3960,24 +3944,20 @@ void main() { // Delete for (int i = 0; i < 6; i += 1) { - sendKeyEventWithCode(_kDelKeyCode, true, false, false); // keydown DEL - await tester.pumpAndSettle(); - sendKeyEventWithCode(_kDelKeyCode, false, false, false); // keyup DEL + await tester.sendKeyEvent(LogicalKeyboardKey.delete); await tester.pumpAndSettle(); } const String expected = 'house\njumped over a mouse'; expect(find.text(expected), findsOneWidget); - sendKeyEventWithCode(_kAKeyCode, true, false, true); // keydown control A + await tester.sendKeyDownEvent(LogicalKeyboardKey.control); + await tester.sendKeyEvent(LogicalKeyboardKey.keyA); await tester.pumpAndSettle(); - sendKeyEventWithCode(_kAKeyCode, false, false, true); // keyup control A + await tester.sendKeyUpEvent(LogicalKeyboardKey.control); await tester.pumpAndSettle(); - - sendKeyEventWithCode(_kDelKeyCode, true, false, false); // keydown DEL - await tester.pumpAndSettle(); - sendKeyEventWithCode(_kDelKeyCode, false, false, false); // keyup DEL + await tester.sendKeyEvent(LogicalKeyboardKey.delete); await tester.pumpAndSettle(); const String expected2 = ''; @@ -4030,10 +4010,12 @@ void main() { await tester.tap(find.byType(TextField).first); await tester.pumpAndSettle(); + await tester.sendKeyDownEvent(LogicalKeyboardKey.shift); for (int i = 0; i < 5; i += 1) { - sendKeyEventWithCode(21, true, true, false); // LEFT_ARROW keydown + await tester.sendKeyEvent(LogicalKeyboardKey.arrowLeft); await tester.pumpAndSettle(); } + await tester.sendKeyUpEvent(LogicalKeyboardKey.shift); expect(c1.selection.extentOffset - c1.selection.baseOffset, 5); @@ -4064,10 +4046,12 @@ void main() { ), ); + await tester.sendKeyDownEvent(LogicalKeyboardKey.shift); for (int i = 0; i < 5; i += 1) { - sendKeyEventWithCode(21, true, true, false); // LEFT_ARROW keydown + await tester.sendKeyEvent(LogicalKeyboardKey.arrowLeft); await tester.pumpAndSettle(); } + await tester.sendKeyUpEvent(LogicalKeyboardKey.shift); expect(c1.selection.extentOffset - c1.selection.baseOffset, 10); }); @@ -4119,10 +4103,12 @@ void main() { await tester.tap(find.byType(TextField).first); await tester.pumpAndSettle(); + await tester.sendKeyDownEvent(LogicalKeyboardKey.shift); for (int i = 0; i < 5; i += 1) { - sendKeyEventWithCode(21, true, true, false); // LEFT_ARROW keydown + await tester.sendKeyEvent(LogicalKeyboardKey.arrowLeft); await tester.pumpAndSettle(); } + await tester.sendKeyUpEvent(LogicalKeyboardKey.shift); expect(c1.selection.extentOffset - c1.selection.baseOffset, 5); expect(c2.selection.extentOffset - c2.selection.baseOffset, 0); @@ -4135,10 +4121,12 @@ void main() { await tester.tap(find.byType(TextField).last); await tester.pumpAndSettle(); + await tester.sendKeyDownEvent(LogicalKeyboardKey.shift); for (int i = 0; i < 5; i += 1) { - sendKeyEventWithCode(21, true, true, false); // LEFT_ARROW keydown + await tester.sendKeyEvent(LogicalKeyboardKey.arrowLeft); await tester.pumpAndSettle(); } + await tester.sendKeyUpEvent(LogicalKeyboardKey.shift); expect(c1.selection.extentOffset - c1.selection.baseOffset, 0); expect(c2.selection.extentOffset - c2.selection.baseOffset, 5); diff --git a/packages/flutter/test/widgets/focus_manager_test.dart b/packages/flutter/test/widgets/focus_manager_test.dart index 683e18de0d2..ad5277ffbb6 100644 --- a/packages/flutter/test/widgets/focus_manager_test.dart +++ b/packages/flutter/test/widgets/focus_manager_test.dart @@ -2,22 +2,13 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'dart:typed_data'; - import 'package:flutter/foundation.dart'; import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; -void sendFakeKeyEvent(Map data) { - ServicesBinding.instance.defaultBinaryMessenger.handlePlatformMessage( - SystemChannels.keyEvent.name, - SystemChannels.keyEvent.codec.encodeMessage(data), - (ByteData data) {}, - ); -} - void main() { final GlobalKey widgetKey = GlobalKey(); Future setupWidget(WidgetTester tester) async { @@ -426,15 +417,9 @@ void main() { return false; } - void sendEvent() { + Future sendEvent() async { receivedAnEvent.clear(); - sendFakeKeyEvent({ - 'type': 'keydown', - 'keymap': 'fuchsia', - 'hidUsage': 0x04, - 'codePoint': 0x64, - 'modifiers': RawKeyEventDataFuchsia.modifierLeftMeta, - }); + await tester.sendKeyEvent(LogicalKeyboardKey.metaLeft, platform: 'fuchsia'); } final BuildContext context = await setupWidget(tester); @@ -465,21 +450,21 @@ void main() { child4.requestFocus(); await tester.pump(); shouldHandle.addAll({scope2, parent2, child2, child4}); - sendEvent(); + await sendEvent(); expect(receivedAnEvent, equals({child4})); shouldHandle.remove(child4); - sendEvent(); + await sendEvent(); expect(receivedAnEvent, equals({parent2})); shouldHandle.remove(parent2); - sendEvent(); + await sendEvent(); expect(receivedAnEvent, equals({scope2})); shouldHandle.clear(); - sendEvent(); + await sendEvent(); expect(receivedAnEvent, isEmpty); child1.requestFocus(); await tester.pump(); shouldHandle.addAll({scope2, parent2, child2, child4}); - sendEvent(); + await sendEvent(); // Since none of the focused nodes handle this event, nothing should // receive it. expect(receivedAnEvent, isEmpty); @@ -500,13 +485,7 @@ void main() { expect(lastMode, isNull); focusManager.highlightStrategy = FocusHighlightStrategy.automatic; expect(focusManager.highlightMode, equals(FocusHighlightMode.touch)); - sendFakeKeyEvent({ - 'type': 'keydown', - 'keymap': 'fuchsia', - 'hidUsage': 0x04, - 'codePoint': 0x64, - 'modifiers': RawKeyEventDataFuchsia.modifierLeftMeta, - }); + await tester.sendKeyEvent(LogicalKeyboardKey.metaLeft, platform: 'fuchsia'); expect(callCount, equals(1)); expect(lastMode, FocusHighlightMode.traditional); expect(focusManager.highlightMode, equals(FocusHighlightMode.traditional)); diff --git a/packages/flutter/test/widgets/raw_keyboard_listener_test.dart b/packages/flutter/test/widgets/raw_keyboard_listener_test.dart index 2f67cfb9b55..5fd4968cc75 100644 --- a/packages/flutter/test/widgets/raw_keyboard_listener_test.dart +++ b/packages/flutter/test/widgets/raw_keyboard_listener_test.dart @@ -2,20 +2,10 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'dart:typed_data'; - import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; -void sendFakeKeyEvent(Map data) { - ServicesBinding.instance.defaultBinaryMessenger.handlePlatformMessage( - SystemChannels.keyEvent.name, - SystemChannels.keyEvent.codec.encodeMessage(data), - (ByteData data) {}, - ); -} - void main() { testWidgets('Can dispose without keyboard', (WidgetTester tester) async { final FocusNode focusNode = FocusNode(); @@ -40,21 +30,15 @@ void main() { focusNode.requestFocus(); await tester.idle(); - sendFakeKeyEvent({ - 'type': 'keydown', - 'keymap': 'fuchsia', - 'hidUsage': 0x04, - 'codePoint': 0x64, - 'modifiers': RawKeyEventDataFuchsia.modifierLeftMeta, - }); + await tester.sendKeyEvent(LogicalKeyboardKey.metaLeft, platform: 'fuchsia'); await tester.idle(); - expect(events.length, 1); + expect(events.length, 2); expect(events[0].runtimeType, equals(RawKeyDownEvent)); expect(events[0].data.runtimeType, equals(RawKeyEventDataFuchsia)); final RawKeyEventDataFuchsia typedData = events[0].data; - expect(typedData.hidUsage, 0x04); - expect(typedData.codePoint, 0x64); + expect(typedData.hidUsage, 0x700e3); + expect(typedData.codePoint, 0x0); expect(typedData.modifiers, RawKeyEventDataFuchsia.modifierLeftMeta); expect(typedData.isModifierPressed(ModifierKey.metaModifier, side: KeyboardSide.left), isTrue); @@ -78,27 +62,15 @@ void main() { focusNode.requestFocus(); await tester.idle(); - sendFakeKeyEvent({ - 'type': 'keydown', - 'keymap': 'fuchsia', - 'hidUsage': 0x04, - 'codePoint': 0x64, - 'modifiers': RawKeyEventDataFuchsia.modifierLeftMeta, - }); + await tester.sendKeyEvent(LogicalKeyboardKey.metaLeft, platform: 'fuchsia'); await tester.idle(); - expect(events.length, 1); + expect(events.length, 2); events.clear(); await tester.pumpWidget(Container()); - sendFakeKeyEvent({ - 'type': 'keydown', - 'keymap': 'fuchsia', - 'hidUsage': 0x04, - 'codePoint': 0x64, - 'modifiers': RawKeyEventDataFuchsia.modifierLeftMeta, - }); + await tester.sendKeyEvent(LogicalKeyboardKey.metaLeft, platform: 'fuchsia'); await tester.idle(); diff --git a/packages/flutter/test/widgets/selectable_text_test.dart b/packages/flutter/test/widgets/selectable_text_test.dart index a8ef552e306..9433cf887e8 100644 --- a/packages/flutter/test/widgets/selectable_text_test.dart +++ b/packages/flutter/test/widgets/selectable_text_test.dart @@ -1247,30 +1247,6 @@ void main() { semantics.dispose(); }); - void sendFakeKeyEvent(Map data) { - ServicesBinding.instance.defaultBinaryMessenger.handlePlatformMessage( - SystemChannels.keyEvent.name, - SystemChannels.keyEvent.codec.encodeMessage(data), - (ByteData data) { }, - ); - } - - void sendKeyEventWithCode(int code, bool down, bool shiftDown, bool ctrlDown) { - - int metaState = shiftDown ? 1 : 0; - if (ctrlDown) - metaState |= 1 << 12; - - sendFakeKeyEvent({ - 'type': down ? 'keydown' : 'keyup', - 'keymap': 'android', - 'keyCode': code, - 'hidUsage': 0x04, - 'codePoint': 0x64, - 'metaState': metaState, - }); - } - group('Keyboard Tests', () { TextEditingController controller; @@ -1300,7 +1276,8 @@ void main() { testWidgets('Shift test 1', (WidgetTester tester) async { await setupWidget(tester, 'a big house'); - sendKeyEventWithCode(21, true, true, false); // LEFT_ARROW keydown, SHIFT_ON + await tester.sendKeyDownEvent(LogicalKeyboardKey.shift); + await tester.sendKeyDownEvent(LogicalKeyboardKey.arrowLeft); expect(controller.selection.extentOffset - controller.selection.baseOffset, 1); }); @@ -1310,7 +1287,8 @@ void main() { controller.selection = const TextSelection.collapsed(offset: 3); await tester.pump(); - sendKeyEventWithCode(22, true, true, false); + await tester.sendKeyDownEvent(LogicalKeyboardKey.shift); + await tester.sendKeyDownEvent(LogicalKeyboardKey.arrowRight); await tester.pumpAndSettle(); expect(controller.selection.extentOffset - controller.selection.baseOffset, 1); }); @@ -1318,7 +1296,9 @@ void main() { testWidgets('Control Shift test', (WidgetTester tester) async { await setupWidget(tester, 'their big house'); - sendKeyEventWithCode(21, true, true, true); // LEFT_ARROW keydown SHIFT_ON, CONTROL_ON + await tester.sendKeyDownEvent(LogicalKeyboardKey.control); + await tester.sendKeyDownEvent(LogicalKeyboardKey.shift); + await tester.sendKeyDownEvent(LogicalKeyboardKey.arrowLeft); await tester.pumpAndSettle(); @@ -1328,14 +1308,16 @@ void main() { testWidgets('Down and up test', (WidgetTester tester) async { await setupWidget(tester, 'a big house'); - sendKeyEventWithCode(19, true, true, false); // UP_ARROW keydown + await tester.sendKeyDownEvent(LogicalKeyboardKey.shift); + await tester.sendKeyDownEvent(LogicalKeyboardKey.arrowUp); await tester.pumpAndSettle(); expect(controller.selection.extentOffset - controller.selection.baseOffset, 11); - sendKeyEventWithCode(19, false, true, false); // UP_ARROW keyup - await tester.pumpAndSettle(); - sendKeyEventWithCode(20, true, true, false); // DOWN_ARROW keydown + await tester.sendKeyUpEvent(LogicalKeyboardKey.arrowUp); + await tester.sendKeyUpEvent(LogicalKeyboardKey.shift); + await tester.sendKeyDownEvent(LogicalKeyboardKey.shift); + await tester.sendKeyDownEvent(LogicalKeyboardKey.arrowDown); await tester.pumpAndSettle(); expect(controller.selection.extentOffset - controller.selection.baseOffset, 0); @@ -1348,51 +1330,51 @@ void main() { await tester.pump(); for (int i = 0; i < 5; i += 1) { - sendKeyEventWithCode(22, true, false, false); // RIGHT_ARROW keydown - await tester.pumpAndSettle(); - sendKeyEventWithCode(22, false, false, false); // RIGHT_ARROW keyup + await tester.sendKeyEvent(LogicalKeyboardKey.arrowRight); await tester.pumpAndSettle(); } - sendKeyEventWithCode(20, true, true, false); // DOWN_ARROW keydown + await tester.sendKeyDownEvent(LogicalKeyboardKey.shift); + await tester.sendKeyEvent(LogicalKeyboardKey.arrowDown); await tester.pumpAndSettle(); - sendKeyEventWithCode(20, false, true, false); // DOWN_ARROW keyup + await tester.sendKeyUpEvent(LogicalKeyboardKey.shift); await tester.pumpAndSettle(); expect(controller.selection.extentOffset - controller.selection.baseOffset, 12); - sendKeyEventWithCode(20, true, true, false); // DOWN_ARROW keydown + await tester.sendKeyDownEvent(LogicalKeyboardKey.shift); + await tester.sendKeyEvent(LogicalKeyboardKey.arrowDown); await tester.pumpAndSettle(); - sendKeyEventWithCode(20, false, true, false); // DOWN_ARROW keyup + await tester.sendKeyUpEvent(LogicalKeyboardKey.shift); await tester.pumpAndSettle(); expect(controller.selection.extentOffset - controller.selection.baseOffset, 32); - sendKeyEventWithCode(19, true, true, false); // UP_ARROW keydown + await tester.sendKeyDownEvent(LogicalKeyboardKey.shift); + await tester.sendKeyEvent(LogicalKeyboardKey.arrowUp); await tester.pumpAndSettle(); - sendKeyEventWithCode(19, false, true, false); // UP_ARROW keyup + await tester.sendKeyUpEvent(LogicalKeyboardKey.shift); await tester.pumpAndSettle(); expect(controller.selection.extentOffset - controller.selection.baseOffset, 12); - sendKeyEventWithCode(19, true, true, false); // UP_ARROW keydown + await tester.sendKeyDownEvent(LogicalKeyboardKey.shift); + await tester.sendKeyEvent(LogicalKeyboardKey.arrowUp); await tester.pumpAndSettle(); - sendKeyEventWithCode(19, false, true, false); // UP_ARROW keyup + await tester.sendKeyUpEvent(LogicalKeyboardKey.shift); await tester.pumpAndSettle(); expect(controller.selection.extentOffset - controller.selection.baseOffset, 0); - sendKeyEventWithCode(19, true, true, false); // UP_ARROW keydown + await tester.sendKeyDownEvent(LogicalKeyboardKey.shift); + await tester.sendKeyEvent(LogicalKeyboardKey.arrowUp); await tester.pumpAndSettle(); - sendKeyEventWithCode(19, false, true, false); // UP_ARROW keyup + await tester.sendKeyUpEvent(LogicalKeyboardKey.shift); await tester.pumpAndSettle(); expect(controller.selection.extentOffset - controller.selection.baseOffset, 5); }); }); - const int _kCKeyCode = 31; - const int _kAKeyCode = 29; - testWidgets('Copy test', (WidgetTester tester) async { final FocusNode focusNode = FocusNode(); @@ -1432,24 +1414,23 @@ void main() { await tester.pump(); // Select the first 5 characters + await tester.sendKeyDownEvent(LogicalKeyboardKey.shift); for (int i = 0; i < 5; i += 1) { - sendKeyEventWithCode(22, true, true, false); // RIGHT_ARROW keydown shift - await tester.pumpAndSettle(); - sendKeyEventWithCode(22, false, false, false); // RIGHT_ARROW keyup + await tester.sendKeyEvent(LogicalKeyboardKey.arrowRight); await tester.pumpAndSettle(); } + await tester.sendKeyUpEvent(LogicalKeyboardKey.shift); // Copy them - sendKeyEventWithCode(_kCKeyCode, true, false, true); // keydown control - await tester.pumpAndSettle(); - sendKeyEventWithCode(_kCKeyCode, false, false, false); // keyup control + await tester.sendKeyDownEvent(LogicalKeyboardKey.controlRight); + await tester.sendKeyDownEvent(LogicalKeyboardKey.keyC); + await tester.sendKeyUpEvent(LogicalKeyboardKey.keyC); + await tester.sendKeyUpEvent(LogicalKeyboardKey.controlRight); await tester.pumpAndSettle(); expect(clipboardContent, 'a big'); - sendKeyEventWithCode(22, true, false, false); // RIGHT_ARROW keydown - await tester.pumpAndSettle(); - sendKeyEventWithCode(22, false, false, false); // RIGHT_ARROW keyup + await tester.sendKeyEvent(LogicalKeyboardKey.arrowRight); await tester.pumpAndSettle(); }); @@ -1479,9 +1460,9 @@ void main() { await tester.pumpAndSettle(); // Select All - sendKeyEventWithCode(_kAKeyCode, true, false, true); // keydown control A - await tester.pumpAndSettle(); - sendKeyEventWithCode(_kAKeyCode, false, false, true); // keyup control A + await tester.sendKeyDownEvent(LogicalKeyboardKey.control); + await tester.sendKeyEvent(LogicalKeyboardKey.keyA); + await tester.sendKeyUpEvent(LogicalKeyboardKey.control); await tester.pumpAndSettle(); expect(controller.selection.baseOffset, 0); @@ -1528,10 +1509,13 @@ void main() { await tester.tap(find.byType(EditableText).first); await tester.pumpAndSettle(); + await tester.sendKeyDownEvent(LogicalKeyboardKey.shift); for (int i = 0; i < 5; i += 1) { - sendKeyEventWithCode(21, true, true, false); // LEFT_ARROW keydown + await tester.sendKeyEvent(LogicalKeyboardKey.arrowLeft); await tester.pumpAndSettle(); } + await tester.sendKeyUpEvent(LogicalKeyboardKey.shift); + await tester.pumpAndSettle(); expect(c1.selection.extentOffset - c1.selection.baseOffset, 5); @@ -1562,10 +1546,12 @@ void main() { ), ); + await tester.sendKeyDownEvent(LogicalKeyboardKey.shift); for (int i = 0; i < 5; i += 1) { - sendKeyEventWithCode(21, true, true, false); // LEFT_ARROW keydown - await tester.pumpAndSettle(); + await tester.sendKeyEvent(LogicalKeyboardKey.arrowLeft); } + await tester.sendKeyUpEvent(LogicalKeyboardKey.shift); + await tester.pumpAndSettle(); editableTextWidget = tester.widget(find.byType(EditableText).last); c1 = editableTextWidget.controller; @@ -1617,10 +1603,13 @@ void main() { await tester.tap(find.byType(SelectableText).first); await tester.pumpAndSettle(); + await tester.sendKeyDownEvent(LogicalKeyboardKey.shift); for (int i = 0; i < 5; i += 1) { - sendKeyEventWithCode(21, true, true, false); // LEFT_ARROW keydown + await tester.sendKeyEvent(LogicalKeyboardKey.arrowLeft); await tester.pumpAndSettle(); } + await tester.sendKeyUpEvent(LogicalKeyboardKey.shift); + await tester.pumpAndSettle(); expect(c1.selection.extentOffset - c1.selection.baseOffset, 5); expect(c2.selection.extentOffset - c2.selection.baseOffset, 0); @@ -1628,10 +1617,13 @@ void main() { await tester.tap(find.byType(SelectableText).last); await tester.pumpAndSettle(); + await tester.sendKeyDownEvent(LogicalKeyboardKey.shift); for (int i = 0; i < 5; i += 1) { - sendKeyEventWithCode(21, true, true, false); // LEFT_ARROW keydown + await tester.sendKeyEvent(LogicalKeyboardKey.arrowLeft); await tester.pumpAndSettle(); } + await tester.sendKeyUpEvent(LogicalKeyboardKey.shift); + await tester.pumpAndSettle(); expect(c1.selection.extentOffset - c1.selection.baseOffset, 0); expect(c2.selection.extentOffset - c2.selection.baseOffset, 5); diff --git a/packages/flutter/test/widgets/shortcuts_test.dart b/packages/flutter/test/widgets/shortcuts_test.dart index 075421c6ebf..51b28623d02 100644 --- a/packages/flutter/test/widgets/shortcuts_test.dart +++ b/packages/flutter/test/widgets/shortcuts_test.dart @@ -6,18 +6,9 @@ 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 data) { - ServicesBinding.instance.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 { @@ -74,34 +65,6 @@ class TestShortcutManager extends ShortcutManager { } } -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({ - '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.', () { @@ -248,7 +211,7 @@ void main() { ); await tester.pump(); expect(Shortcuts.of(containerKey.currentContext), isNotNull); - testKeypress(LogicalKeyboardKey.shiftLeft); + await tester.sendKeyDownEvent(LogicalKeyboardKey.shiftLeft); expect(invoked, isTrue); expect(pressedKeys, equals([LogicalKeyboardKey.shiftLeft])); }); @@ -284,7 +247,7 @@ void main() { ); await tester.pump(); expect(Shortcuts.of(containerKey.currentContext), isNotNull); - testKeypress(LogicalKeyboardKey.shiftLeft); + await tester.sendKeyDownEvent(LogicalKeyboardKey.shiftLeft); expect(invoked, isTrue); expect(pressedKeys, equals([LogicalKeyboardKey.shiftLeft])); }); diff --git a/packages/flutter_test/lib/flutter_test.dart b/packages/flutter_test/lib/flutter_test.dart index 7f688b58248..332c1370471 100644 --- a/packages/flutter_test/lib/flutter_test.dart +++ b/packages/flutter_test/lib/flutter_test.dart @@ -51,6 +51,7 @@ export 'src/accessibility.dart'; export 'src/all_elements.dart'; export 'src/binding.dart'; export 'src/controller.dart'; +export 'src/event_simulation.dart'; export 'src/finders.dart'; export 'src/goldens.dart'; export 'src/matchers.dart'; diff --git a/packages/flutter_test/lib/src/binding.dart b/packages/flutter_test/lib/src/binding.dart index 71ae4e42666..3a2d83f5bf0 100644 --- a/packages/flutter_test/lib/src/binding.dart +++ b/packages/flutter_test/lib/src/binding.dart @@ -796,6 +796,11 @@ abstract class TestWidgetsFlutterBinding extends BindingBase _pendingExceptionDetails = null; _parentZone = null; buildOwner.focusManager = FocusManager(); + // Disabling the warning because @visibleForTesting doesn't take the testing + // framework itself into account, but we don't want it visible outside of + // tests. + // ignore: invalid_use_of_visible_for_testing_member + RawKeyboard.instance.clearKeysPressed(); assert(!RendererBinding.instance.mouseTracker.mouseIsConnected, 'The MouseTracker thinks that there is still a mouse connected, which indicates that a ' 'test has not removed the mouse pointer which it added. Call removePointer on the ' diff --git a/packages/flutter_test/lib/src/event_simulation.dart b/packages/flutter_test/lib/src/event_simulation.dart new file mode 100644 index 00000000000..e3b70deab5e --- /dev/null +++ b/packages/flutter_test/lib/src/event_simulation.dart @@ -0,0 +1,370 @@ +// Copyright 2019 The Chromium 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 'dart:async'; +import 'dart:io'; + +import 'package:flutter/services.dart'; +import 'test_async_utils.dart'; + +// For the synonym keys, just return the left version of it. +LogicalKeyboardKey _getSynonym(LogicalKeyboardKey origKey) { + if (origKey == LogicalKeyboardKey.shift) { + return LogicalKeyboardKey.shiftLeft; + } + if (origKey == LogicalKeyboardKey.alt) { + return LogicalKeyboardKey.altLeft; + } + if (origKey == LogicalKeyboardKey.meta) { + return LogicalKeyboardKey.metaLeft; + } + if (origKey == LogicalKeyboardKey.control) { + return LogicalKeyboardKey.controlLeft; + } + return origKey; +} + +bool _osIsSupported(String platform) { + switch (platform) { + case 'android': + case 'fuchsia': + case 'macos': + case 'linux': + return true; + } + return false; +} + +int _getScanCode(LogicalKeyboardKey key, String platform) { + assert(_osIsSupported(platform), 'Platform $platform not supported for key simulation'); + int scanCode; + Map map; + switch (platform) { + case 'android': + map = kAndroidToPhysicalKey; + break; + case 'fuchsia': + map = kFuchsiaToPhysicalKey; + break; + case 'macos': + map = kMacOsToPhysicalKey; + break; + case 'linux': + map = kLinuxToPhysicalKey; + break; + } + for (int code in map.keys) { + if (key.debugName == map[code].debugName) { + scanCode = code; + break; + } + } + return scanCode; +} + +int _getKeyCode(LogicalKeyboardKey key, String platform) { + assert(_osIsSupported(platform), 'Platform $platform not supported for key simulation'); + int keyCode; + Map map; + switch (platform) { + case 'android': + map = kAndroidToLogicalKey; + break; + case 'fuchsia': + map = kFuchsiaToLogicalKey; + break; + case 'macos': + // macOS doesn't do key codes, just scan codes. + return null; + case 'linux': + map = kGlfwToLogicalKey; + break; + } + for (int code in map.keys) { + if (key.debugName == map[code].debugName) { + keyCode = code; + break; + } + } + return keyCode; +} + +Map _getKeyData(LogicalKeyboardKey key, {String platform, bool isDown = true}) { + assert(_osIsSupported(platform), 'Platform $platform not supported for key simulation'); + + key = _getSynonym(key); + + assert(key.debugName != null); + final int keyCode = platform == 'macos' ? -1 : _getKeyCode(key, platform); + assert(platform == 'macos' || keyCode != null, 'Key $key not found in $platform keyCode map'); + final int scanCode = _getScanCode(key, platform); + assert(scanCode != null, 'Physical key for $key not found in $platform scanCode map'); + + final Map result = { + 'type': isDown ? 'keydown' : 'keyup', + 'keymap': platform, + 'character': key.keyLabel, + }; + + switch (platform) { + case 'android': + result['keyCode'] = keyCode; + result['codePoint'] = key.keyLabel?.codeUnitAt(0); + result['scanCode'] = scanCode; + result['metaState'] = _getAndroidModifierFlags(key, isDown); + break; + case 'fuchsia': + result['hidUsage'] = key.keyId & LogicalKeyboardKey.hidPlane != 0 ? key.keyId & LogicalKeyboardKey.valueMask : null; + result['codePoint'] = key.keyLabel?.codeUnitAt(0); + result['modifiers'] = _getFuchsiaModifierFlags(key, isDown); + break; + case 'linux': + result['toolkit'] = 'glfw'; + result['keyCode'] = keyCode; + result['scanCode'] = scanCode; + result['modifiers'] = _getGlfwModifierFlags(key, isDown); + break; + case 'macos': + result['keyCode'] = scanCode; + result['characters'] = key.keyLabel; + result['charactersIgnoringModifiers'] = key.keyLabel; + result['modifiers'] = _getMacOsModifierFlags(key, isDown); + break; + } + return result; +} + +int _getAndroidModifierFlags(LogicalKeyboardKey newKey, bool isDown) { + int result = 0; + final Set pressed = RawKeyboard.instance.keysPressed; + if (isDown) { + pressed.add(newKey); + } else { + pressed.remove(newKey); + } + if (pressed.contains(LogicalKeyboardKey.shiftLeft)) { + result |= RawKeyEventDataAndroid.modifierLeftShift | RawKeyEventDataAndroid.modifierShift; + } + if (pressed.contains(LogicalKeyboardKey.shiftRight)) { + result |= RawKeyEventDataAndroid.modifierRightShift | RawKeyEventDataAndroid.modifierShift; + } + if (pressed.contains(LogicalKeyboardKey.metaLeft)) { + result |= RawKeyEventDataAndroid.modifierLeftMeta | RawKeyEventDataAndroid.modifierMeta; + } + if (pressed.contains(LogicalKeyboardKey.metaRight)) { + result |= RawKeyEventDataAndroid.modifierRightMeta | RawKeyEventDataAndroid.modifierMeta; + } + if (pressed.contains(LogicalKeyboardKey.controlLeft)) { + result |= RawKeyEventDataAndroid.modifierLeftControl | RawKeyEventDataAndroid.modifierControl; + } + if (pressed.contains(LogicalKeyboardKey.controlRight)) { + result |= RawKeyEventDataAndroid.modifierRightControl | RawKeyEventDataAndroid.modifierControl; + } + if (pressed.contains(LogicalKeyboardKey.altLeft)) { + result |= RawKeyEventDataAndroid.modifierLeftAlt | RawKeyEventDataAndroid.modifierAlt; + } + if (pressed.contains(LogicalKeyboardKey.altRight)) { + result |= RawKeyEventDataAndroid.modifierRightAlt | RawKeyEventDataAndroid.modifierAlt; + } + if (pressed.contains(LogicalKeyboardKey.fn)) { + result |= RawKeyEventDataAndroid.modifierFunction; + } + if (pressed.contains(LogicalKeyboardKey.scrollLock)) { + result |= RawKeyEventDataAndroid.modifierScrollLock; + } + if (pressed.contains(LogicalKeyboardKey.numLock)) { + result |= RawKeyEventDataAndroid.modifierNumLock; + } + if (pressed.contains(LogicalKeyboardKey.capsLock)) { + result |= RawKeyEventDataAndroid.modifierCapsLock; + } + return result; +} + +int _getGlfwModifierFlags(LogicalKeyboardKey newKey, bool isDown) { + int result = 0; + final Set pressed = RawKeyboard.instance.keysPressed; + if (isDown) { + pressed.add(newKey); + } else { + pressed.remove(newKey); + } + if (pressed.contains(LogicalKeyboardKey.shiftLeft) || pressed.contains(LogicalKeyboardKey.shiftRight)) { + result |= GLFWKeyHelper.modifierShift; + } + if (pressed.contains(LogicalKeyboardKey.metaLeft) || pressed.contains(LogicalKeyboardKey.metaRight)) { + result |= GLFWKeyHelper.modifierMeta; + } + if (pressed.contains(LogicalKeyboardKey.controlLeft) || pressed.contains(LogicalKeyboardKey.controlRight)) { + result |= GLFWKeyHelper.modifierControl; + } + if (pressed.contains(LogicalKeyboardKey.altLeft) || pressed.contains(LogicalKeyboardKey.altRight)) { + result |= GLFWKeyHelper.modifierAlt; + } + if (pressed.contains(LogicalKeyboardKey.capsLock)) { + result |= GLFWKeyHelper.modifierCapsLock; + } + return result; +} + +int _getFuchsiaModifierFlags(LogicalKeyboardKey newKey, bool isDown) { + int result = 0; + final Set pressed = RawKeyboard.instance.keysPressed; + if (isDown) { + pressed.add(newKey); + } else { + pressed.remove(newKey); + } + if (pressed.contains(LogicalKeyboardKey.shiftLeft)) { + result |= RawKeyEventDataFuchsia.modifierLeftShift; + } + if (pressed.contains(LogicalKeyboardKey.shiftRight)) { + result |= RawKeyEventDataFuchsia.modifierRightShift; + } + if (pressed.contains(LogicalKeyboardKey.metaLeft)) { + result |= RawKeyEventDataFuchsia.modifierLeftMeta; + } + if (pressed.contains(LogicalKeyboardKey.metaRight)) { + result |= RawKeyEventDataFuchsia.modifierRightMeta; + } + if (pressed.contains(LogicalKeyboardKey.controlLeft)) { + result |= RawKeyEventDataFuchsia.modifierLeftControl; + } + if (pressed.contains(LogicalKeyboardKey.controlRight)) { + result |= RawKeyEventDataFuchsia.modifierRightControl; + } + if (pressed.contains(LogicalKeyboardKey.altLeft)) { + result |= RawKeyEventDataFuchsia.modifierLeftAlt; + } + if (pressed.contains(LogicalKeyboardKey.altRight)) { + result |= RawKeyEventDataFuchsia.modifierRightAlt; + } + if (pressed.contains(LogicalKeyboardKey.capsLock)) { + result |= RawKeyEventDataFuchsia.modifierCapsLock; + } + return result; +} + +int _getMacOsModifierFlags(LogicalKeyboardKey newKey, bool isDown) { + int result = 0; + final Set pressed = RawKeyboard.instance.keysPressed; + if (isDown) { + pressed.add(newKey); + } else { + pressed.remove(newKey); + } + if (pressed.contains(LogicalKeyboardKey.shiftLeft)) { + result |= RawKeyEventDataMacOs.modifierLeftShift | RawKeyEventDataMacOs.modifierShift; + } + if (pressed.contains(LogicalKeyboardKey.shiftRight)) { + result |= RawKeyEventDataMacOs.modifierRightShift | RawKeyEventDataMacOs.modifierShift; + } + if (pressed.contains(LogicalKeyboardKey.metaLeft)) { + result |= RawKeyEventDataMacOs.modifierLeftCommand | RawKeyEventDataMacOs.modifierCommand; + } + if (pressed.contains(LogicalKeyboardKey.metaRight)) { + result |= RawKeyEventDataMacOs.modifierRightCommand | RawKeyEventDataMacOs.modifierCommand; + } + if (pressed.contains(LogicalKeyboardKey.controlLeft)) { + result |= RawKeyEventDataMacOs.modifierLeftControl | RawKeyEventDataMacOs.modifierControl; + } + if (pressed.contains(LogicalKeyboardKey.controlRight)) { + result |= RawKeyEventDataMacOs.modifierRightControl | RawKeyEventDataMacOs.modifierControl; + } + if (pressed.contains(LogicalKeyboardKey.altLeft)) { + result |= RawKeyEventDataMacOs.modifierLeftOption | RawKeyEventDataMacOs.modifierOption; + } + if (pressed.contains(LogicalKeyboardKey.altRight)) { + result |= RawKeyEventDataMacOs.modifierRightOption | RawKeyEventDataMacOs.modifierOption; + } + final Set functionKeys = { + LogicalKeyboardKey.f1, + LogicalKeyboardKey.f2, + LogicalKeyboardKey.f3, + LogicalKeyboardKey.f4, + LogicalKeyboardKey.f5, + LogicalKeyboardKey.f6, + LogicalKeyboardKey.f7, + LogicalKeyboardKey.f8, + LogicalKeyboardKey.f9, + LogicalKeyboardKey.f10, + LogicalKeyboardKey.f11, + LogicalKeyboardKey.f12, + LogicalKeyboardKey.f13, + LogicalKeyboardKey.f14, + LogicalKeyboardKey.f15, + LogicalKeyboardKey.f16, + LogicalKeyboardKey.f17, + LogicalKeyboardKey.f18, + LogicalKeyboardKey.f19, + LogicalKeyboardKey.f20, + LogicalKeyboardKey.f21, + }; + if (pressed.intersection(functionKeys).isNotEmpty) { + result |= RawKeyEventDataMacOs.modifierFunction; + } + if (pressed.intersection(kMacOsNumPadMap.values.toSet()).isNotEmpty) { + result |= RawKeyEventDataMacOs.modifierNumericPad; + } + if (pressed.contains(LogicalKeyboardKey.capsLock)) { + result |= RawKeyEventDataMacOs.modifierCapsLock; + } + return result; +} + +/// Simulates sending a hardware key down event through the system channel. +/// +/// This only simulates key presses coming from a physical keyboard, not from a +/// soft keyboard. +/// +/// Specify `platform` as one of the platforms allowed in +/// [Platform.operatingSystem] to make the event appear to be from that type of +/// system. Defaults to the operating system that the test is running on. Some +/// platforms (e.g. Windows, iOS) are not yet supported. +/// +/// Keys that are down when the test completes are cleared after each test. +/// +/// See also: +/// +/// - [simulateKeyUpEvent] to simulate the corresponding key up event. +Future simulateKeyDownEvent(LogicalKeyboardKey key, {String platform}) async { + return TestAsyncUtils.guard(() async { + platform ??= Platform.operatingSystem; + assert(_osIsSupported(platform), 'Platform $platform not supported for key simulation'); + + final Map data = _getKeyData(key, platform: platform, isDown: true); + await ServicesBinding.instance.defaultBinaryMessenger.handlePlatformMessage( + SystemChannels.keyEvent.name, + SystemChannels.keyEvent.codec.encodeMessage(data), + (ByteData data) {}, + ); + }); +} + +/// Simulates sending a hardware key up event through the system channel. +/// +/// This only simulates key presses coming from a physical keyboard, not from a +/// soft keyboard. +/// +/// Specify `platform` as one of the platforms allowed in +/// [Platform.operatingSystem] to make the event appear to be from that type of +/// system. Defaults to the operating system that the test is running on. Some +/// platforms (e.g. Windows, iOS) are not yet supported. +/// +/// See also: +/// +/// - [simulateKeyDownEvent] to simulate the corresponding key down event. +Future simulateKeyUpEvent(LogicalKeyboardKey key, {String platform}) async { + return TestAsyncUtils.guard(() async { + platform ??= Platform.operatingSystem; + assert(_osIsSupported(platform), 'Platform $platform not supported for key simulation'); + + final Map data = _getKeyData(key, platform: platform, isDown: false); + await ServicesBinding.instance.defaultBinaryMessenger.handlePlatformMessage( + SystemChannels.keyEvent.name, + SystemChannels.keyEvent.codec.encodeMessage(data), + (ByteData data) {}, + ); + }); +} diff --git a/packages/flutter_test/lib/src/widget_tester.dart b/packages/flutter_test/lib/src/widget_tester.dart index 5b9fdb10738..ba4439f76db 100644 --- a/packages/flutter_test/lib/src/widget_tester.dart +++ b/packages/flutter_test/lib/src/widget_tester.dart @@ -9,6 +9,7 @@ import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/scheduler.dart'; +import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; import 'package:meta/meta.dart'; import 'package:test_api/test_api.dart' as test_package; @@ -16,6 +17,7 @@ import 'package:test_api/test_api.dart' as test_package; import 'all_elements.dart'; import 'binding.dart'; import 'controller.dart'; +import 'event_simulation.dart'; import 'finders.dart'; import 'matchers.dart'; import 'test_async_utils.dart'; @@ -719,6 +721,74 @@ class WidgetTester extends WidgetController implements HitTestDispatcher, Ticker }); } + /// Simulates sending physical key down and up events through the system channel. + /// + /// This only simulates key events coming from a physical keyboard, not from a + /// soft keyboard. + /// + /// Specify `platform` as one of the platforms allowed in + /// [Platform.operatingSystem] to make the event appear to be from that type + /// of system. Defaults to "android". Must not be null. Some platforms (e.g. + /// Windows, iOS) are not yet supported. + /// + /// Keys that are down when the test completes are cleared after each test. + /// + /// This method sends both the key down and the key up events, to simulate a + /// key press. To simulate individual down and/or up events, see + /// [sendKeyDownEvent] and [sendKeyUpEvent]. + /// + /// See also: + /// + /// - [sendKeyDownEvent] to simulate only a key down event. + /// - [sendKeyUpEvent] to simulate only a key up event. + Future sendKeyEvent(LogicalKeyboardKey key, { String platform = 'android' }) async { + assert(platform != null); + await simulateKeyDownEvent(key, platform: platform); + // Internally wrapped in async guard. + return simulateKeyUpEvent(key, platform: platform); + } + + /// Simulates sending a physical key down event through the system channel. + /// + /// This only simulates key down events coming from a physical keyboard, not + /// from a soft keyboard. + /// + /// Specify `platform` as one of the platforms allowed in + /// [Platform.operatingSystem] to make the event appear to be from that type + /// of system. Defaults to "android". Must not be null. Some platforms (e.g. + /// Windows, iOS) are not yet supported. + /// + /// Keys that are down when the test completes are cleared after each test. + /// + /// See also: + /// + /// - [sendKeyUpEvent] to simulate the corresponding key up event. + /// - [sendKeyEvent] to simulate both the key up and key down in the same call. + Future sendKeyDownEvent(LogicalKeyboardKey key, { String platform = 'android' }) async { + assert(platform != null); + // Internally wrapped in async guard. + return simulateKeyDownEvent(key, platform: platform); + } + + /// Simulates sending a physical key up event through the system channel. + /// + /// This only simulates key up events coming from a physical keyboard, + /// not from a soft keyboard. + /// + /// Specify `platform` as one of the platforms allowed in + /// [Platform.operatingSystem] to make the event appear to be from that type + /// of system. Defaults to "android". May not be null. + /// + /// See also: + /// + /// - [sendKeyDownEvent] to simulate the corresponding key down event. + /// - [sendKeyEvent] to simulate both the key up and key down in the same call. + Future sendKeyUpEvent(LogicalKeyboardKey key, { String platform = 'android' }) async { + assert(platform != null); + // Internally wrapped in async guard. + return simulateKeyUpEvent(key, platform: platform); + } + /// Makes an effort to dismiss the current page with a Material [Scaffold] or /// a [CupertinoPageScaffold]. /// diff --git a/packages/flutter_test/test/event_simulation_test.dart b/packages/flutter_test/test/event_simulation_test.dart new file mode 100644 index 00000000000..1632e95cfc8 --- /dev/null +++ b/packages/flutter_test/test/event_simulation_test.dart @@ -0,0 +1,55 @@ +// Copyright 2019 The Chromium 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/services.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_test/flutter_test.dart'; + +const List platforms = ['linux', 'macos', 'android', 'fuchsia']; + +void main() { + testWidgets('simulates keyboard events', (WidgetTester tester) async { + final List events = []; + + final FocusNode focusNode = FocusNode(); + + await tester.pumpWidget( + RawKeyboardListener( + focusNode: focusNode, + onKey: events.add, + child: Container(), + ), + ); + + focusNode.requestFocus(); + await tester.idle(); + + for (String platform in platforms) { + await tester.sendKeyEvent(LogicalKeyboardKey.shiftLeft, platform: platform); + await tester.sendKeyEvent(LogicalKeyboardKey.shift, platform: platform); + await tester.sendKeyDownEvent(LogicalKeyboardKey.keyA, platform: platform); + await tester.sendKeyUpEvent(LogicalKeyboardKey.keyA, platform: platform); + await tester.sendKeyDownEvent(LogicalKeyboardKey.numpad1, platform: platform); + await tester.sendKeyUpEvent(LogicalKeyboardKey.numpad1, platform: platform); + await tester.idle(); + + expect(events.length, 8); + for (int i = 0; i < events.length; ++i) { + final bool isEven = i % 2 == 0; + if (isEven) { + expect(events[i].runtimeType, equals(RawKeyDownEvent)); + } else { + expect(events[i].runtimeType, equals(RawKeyUpEvent)); + } + if (i < 4) { + expect(events[i].data.isModifierPressed(ModifierKey.shiftModifier, side: KeyboardSide.left), equals(isEven)); + } + } + events.clear(); + } + + await tester.pumpWidget(Container()); + focusNode.dispose(); + }); +}