Unverified Commit 0e6cb28d authored by Greg Spencer's avatar Greg Spencer Committed by GitHub

Add fake keyboard key generation to the testing framework (#40706)

There were four or five different implementations in various tests for sendFakeKeyEvent, which roughly all did the same thing. I was going to add yet another one, and decided that it needed to be generalized and centralized. This replaces those instances with something that just takes a LogicalKeyboardKey so that it's self-documenting, and can be used with multiple platforms.

This adds two functions to widget tester: sendKeyDownEvent and sendKeyUpEvent which simulate key up/down from a physical keyboard. It also adds global functions simulateKeyDownEvent and simulateKeyUpEvent that can be called without a widget tester. All are async functions protected by the async guard.
parent 4815b26d
......@@ -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';
......
......@@ -512,4 +512,12 @@ class RawKeyboard {
Set<LogicalKeyboardKey> 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();
}
}
......@@ -3531,30 +3531,6 @@ void main() {
expect(focusNode2.hasPrimaryFocus, isFalse);
});
void sendFakeKeyEvent(Map<String, dynamic> 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(<String, dynamic>{
'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);
......
......@@ -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<String, dynamic> data) {
ServicesBinding.instance.defaultBinaryMessenger.handlePlatformMessage(
SystemChannels.keyEvent.name,
SystemChannels.keyEvent.codec.encodeMessage(data),
(ByteData data) {},
);
}
void main() {
final GlobalKey widgetKey = GlobalKey();
Future<BuildContext> setupWidget(WidgetTester tester) async {
......@@ -426,15 +417,9 @@ void main() {
return false;
}
void sendEvent() {
Future<void> sendEvent() async {
receivedAnEvent.clear();
sendFakeKeyEvent(<String, dynamic>{
'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(<FocusNode>{scope2, parent2, child2, child4});
sendEvent();
await sendEvent();
expect(receivedAnEvent, equals(<FocusNode>{child4}));
shouldHandle.remove(child4);
sendEvent();
await sendEvent();
expect(receivedAnEvent, equals(<FocusNode>{parent2}));
shouldHandle.remove(parent2);
sendEvent();
await sendEvent();
expect(receivedAnEvent, equals(<FocusNode>{scope2}));
shouldHandle.clear();
sendEvent();
await sendEvent();
expect(receivedAnEvent, isEmpty);
child1.requestFocus();
await tester.pump();
shouldHandle.addAll(<FocusNode>{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(<String, dynamic>{
'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));
......
......@@ -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<String, dynamic> 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(<String, dynamic>{
'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(<String, dynamic>{
'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(<String, dynamic>{
'type': 'keydown',
'keymap': 'fuchsia',
'hidUsage': 0x04,
'codePoint': 0x64,
'modifiers': RawKeyEventDataFuchsia.modifierLeftMeta,
});
await tester.sendKeyEvent(LogicalKeyboardKey.metaLeft, platform: 'fuchsia');
await tester.idle();
......
......@@ -1247,30 +1247,6 @@ void main() {
semantics.dispose();
});
void sendFakeKeyEvent(Map<String, dynamic> 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(<String, dynamic>{
'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);
......
......@@ -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<String, dynamic> 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(<String, dynamic>{
'type': 'keydown',
'keymap': 'android',
'keyCode': keyCode,
'plainCodePoint': 0,
'codePoint': 0,
'character': null,
'scanCode': scanCode,
'metaState': 0,
});
}
void main() {
group(LogicalKeySet, () {
test('$LogicalKeySet passes parameters correctly.', () {
......@@ -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>[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>[LogicalKeyboardKey.shiftLeft]));
});
......
......@@ -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';
......
......@@ -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 '
......
// 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<int, PhysicalKeyboardKey> 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<int, LogicalKeyboardKey> 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<String, dynamic> _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<String, dynamic> result = <String, dynamic>{
'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<LogicalKeyboardKey> 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<LogicalKeyboardKey> 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<LogicalKeyboardKey> 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<LogicalKeyboardKey> 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<LogicalKeyboardKey> functionKeys = <LogicalKeyboardKey>{
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<void> simulateKeyDownEvent(LogicalKeyboardKey key, {String platform}) async {
return TestAsyncUtils.guard<void>(() async {
platform ??= Platform.operatingSystem;
assert(_osIsSupported(platform), 'Platform $platform not supported for key simulation');
final Map<String, dynamic> 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<void> simulateKeyUpEvent(LogicalKeyboardKey key, {String platform}) async {
return TestAsyncUtils.guard<void>(() async {
platform ??= Platform.operatingSystem;
assert(_osIsSupported(platform), 'Platform $platform not supported for key simulation');
final Map<String, dynamic> data = _getKeyData(key, platform: platform, isDown: false);
await ServicesBinding.instance.defaultBinaryMessenger.handlePlatformMessage(
SystemChannels.keyEvent.name,
SystemChannels.keyEvent.codec.encodeMessage(data),
(ByteData data) {},
);
});
}
......@@ -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<void> 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<void> 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<void> 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].
///
......
// 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<String> platforms = <String>['linux', 'macos', 'android', 'fuchsia'];
void main() {
testWidgets('simulates keyboard events', (WidgetTester tester) async {
final List<RawKeyEvent> events = <RawKeyEvent>[];
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();
});
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment