5 #include <Carbon/Carbon.h>
6 #import <Foundation/Foundation.h>
7 #import <OCMock/OCMock.h>
12 #include "flutter/shell/platform/embedder/test_utils/key_codes.g.h"
13 #import "flutter/testing/testing.h"
14 #include "third_party/googletest/googletest/include/gtest/gtest.h"
18 using flutter::testing::keycodes::kLogicalBracketLeft;
19 using flutter::testing::keycodes::kLogicalDigit1;
20 using flutter::testing::keycodes::kLogicalDigit2;
21 using flutter::testing::keycodes::kLogicalKeyA;
22 using flutter::testing::keycodes::kLogicalKeyM;
23 using flutter::testing::keycodes::kLogicalKeyQ;
24 using flutter::testing::keycodes::kLogicalKeyT;
25 using flutter::testing::keycodes::kPhysicalKeyA;
29 typedef BOOL (^BoolGetter)();
31 typedef void (^AsyncEmbedderCallbackHandler)(
const FlutterKeyEvent* event,
33 typedef BOOL (^TextInputCallback)(NSEvent*);
39 constexpr uint16_t kKeyCodeEmpty = 0x00;
42 constexpr uint32_t kEmbedderCall = 0x1;
43 constexpr uint32_t kChannelCall = 0x2;
44 constexpr uint32_t kTextCall = 0x4;
54 typedef const std::array<uint32_t, 256> MockLayoutData;
58 MockLayoutData kUsLayout = {
60 0x00061, 0x00041, 0x00073, 0x00053, 0x00064, 0x00044, 0x00066, 0x00046,
61 0x00068, 0x00048, 0x00067, 0x00047, 0x0007a, 0x0005a, 0x00078, 0x00058,
62 0x00063, 0x00043, 0x00076, 0x00056, 0x000a7, 0x000b1, 0x00062, 0x00042,
63 0x00071, 0x00051, 0x00077, 0x00057, 0x00065, 0x00045, 0x00072, 0x00052,
64 0x00079, 0x00059, 0x00074, 0x00054, 0x00031, 0x00021, 0x00032, 0x00040,
65 0x00033, 0x00023, 0x00034, 0x00024, 0x00036, 0x0005e, 0x00035, 0x00025,
66 0x0003d, 0x0002b, 0x00039, 0x00028, 0x00037, 0x00026, 0x0002d, 0x0005f,
67 0x00038, 0x0002a, 0x00030, 0x00029, 0x0005d, 0x0007d, 0x0006f, 0x0004f,
68 0x00075, 0x00055, 0x0005b, 0x0007b, 0x00069, 0x00049, 0x00070, 0x00050,
69 0x00000, 0x00000, 0x0006c, 0x0004c, 0x0006a, 0x0004a, 0x00027, 0x00022,
70 0x0006b, 0x0004b, 0x0003b, 0x0003a, 0x0005c, 0x0007c, 0x0002c, 0x0003c,
71 0x0002f, 0x0003f, 0x0006e, 0x0004e, 0x0006d, 0x0004d, 0x0002e, 0x0003e,
72 0x00000, 0x00000, 0x00020, 0x00020, 0x00060, 0x0007e,
75 MockLayoutData kFrenchLayout = {
77 0x00071, 0x00051, 0x00073, 0x00053, 0x00064, 0x00044, 0x00066, 0x00046,
78 0x00068, 0x00048, 0x00067, 0x00047, 0x00077, 0x00057, 0x00078, 0x00058,
79 0x00063, 0x00043, 0x00076, 0x00056, 0x00040, 0x00023, 0x00062, 0x00042,
80 0x00061, 0x00041, 0x0007a, 0x0005a, 0x00065, 0x00045, 0x00072, 0x00052,
81 0x00079, 0x00059, 0x00074, 0x00054, 0x00026, 0x00031, 0x000e9, 0x00032,
82 0x00022, 0x00033, 0x00027, 0x00034, 0x000a7, 0x00036, 0x00028, 0x00035,
83 0x0002d, 0x0005f, 0x000e7, 0x00039, 0x000e8, 0x00037, 0x00029, 0x000b0,
84 0x00021, 0x00038, 0x000e0, 0x00030, 0x00024, 0x0002a, 0x0006f, 0x0004f,
85 0x00075, 0x00055, 0x1005e, 0x100a8, 0x00069, 0x00049, 0x00070, 0x00050,
86 0x00000, 0x00000, 0x0006c, 0x0004c, 0x0006a, 0x0004a, 0x000f9, 0x00025,
87 0x0006b, 0x0004b, 0x0006d, 0x0004d, 0x10060, 0x000a3, 0x0003b, 0x0002e,
88 0x0003d, 0x0002b, 0x0006e, 0x0004e, 0x0002c, 0x0003f, 0x0003a, 0x0002f,
89 0x00000, 0x00000, 0x00020, 0x00020, 0x0003c, 0x0003e,
92 MockLayoutData kRussianLayout = {
94 0x00444, 0x00424, 0x0044b, 0x0042b, 0x00432, 0x00412, 0x00430, 0x00410,
95 0x00440, 0x00420, 0x0043f, 0x0041f, 0x0044f, 0x0042f, 0x00447, 0x00427,
96 0x00441, 0x00421, 0x0043c, 0x0041c, 0x0003e, 0x0003c, 0x00438, 0x00418,
97 0x00439, 0x00419, 0x00446, 0x00426, 0x00443, 0x00423, 0x0043a, 0x0041a,
98 0x0043d, 0x0041d, 0x00435, 0x00415, 0x00031, 0x00021, 0x00032, 0x00022,
99 0x00033, 0x02116, 0x00034, 0x00025, 0x00036, 0x0002c, 0x00035, 0x0003a,
100 0x0003d, 0x0002b, 0x00039, 0x00028, 0x00037, 0x0002e, 0x0002d, 0x0005f,
101 0x00038, 0x0003b, 0x00030, 0x00029, 0x0044a, 0x0042a, 0x00449, 0x00429,
102 0x00433, 0x00413, 0x00445, 0x00425, 0x00448, 0x00428, 0x00437, 0x00417,
103 0x00000, 0x00000, 0x00434, 0x00414, 0x0043e, 0x0041e, 0x0044d, 0x0042d,
104 0x0043b, 0x0041b, 0x00436, 0x00416, 0x00451, 0x00401, 0x00431, 0x00411,
105 0x0002f, 0x0003f, 0x00442, 0x00422, 0x0044c, 0x0042c, 0x0044e, 0x0042e,
106 0x00000, 0x00000, 0x00020, 0x00020, 0x0005d, 0x0005b,
109 MockLayoutData kKhmerLayout = {
111 0x017b6, 0x017ab, 0x0179f, 0x017c3, 0x0178a, 0x0178c, 0x01790, 0x01792,
112 0x017a0, 0x017c7, 0x01784, 0x017a2, 0x0178b, 0x0178d, 0x01781, 0x01783,
113 0x01785, 0x01787, 0x0179c, 0x017c8, 0x00000, 0x00000, 0x01794, 0x01796,
114 0x01786, 0x01788, 0x017b9, 0x017ba, 0x017c1, 0x017c2, 0x0179a, 0x017ac,
115 0x01799, 0x017bd, 0x0178f, 0x01791, 0x017e1, 0x00021, 0x017e2, 0x017d7,
116 0x017e3, 0x00022, 0x017e4, 0x017db, 0x017e6, 0x017cd, 0x017e5, 0x00025,
117 0x017b2, 0x017ce, 0x017e9, 0x017b0, 0x017e7, 0x017d0, 0x017a5, 0x017cc,
118 0x017e8, 0x017cf, 0x017e0, 0x017b3, 0x017aa, 0x017a7, 0x017c4, 0x017c5,
119 0x017bb, 0x017bc, 0x017c0, 0x017bf, 0x017b7, 0x017b8, 0x01795, 0x01797,
120 0x00000, 0x00000, 0x0179b, 0x017a1, 0x017d2, 0x01789, 0x017cb, 0x017c9,
121 0x01780, 0x01782, 0x017be, 0x017d6, 0x017ad, 0x017ae, 0x017a6, 0x017b1,
122 0x017ca, 0x017af, 0x01793, 0x0178e, 0x01798, 0x017c6, 0x017d4, 0x017d5,
123 0x00000, 0x00000, 0x00020, 0x0200b, 0x000ab, 0x000bb,
126 NSEvent* keyDownEvent(
unsigned short keyCode, NSString* chars =
@"", NSString* charsUnmod =
@"") {
127 return [NSEvent keyEventWithType:NSEventTypeKeyDown
134 charactersIgnoringModifiers:charsUnmod
139 NSEvent* keyUpEvent(
unsigned short keyCode) {
140 return [NSEvent keyEventWithType:NSEventTypeKeyUp
147 charactersIgnoringModifiers:
@""
152 id checkKeyDownEvent(
unsigned short keyCode) {
153 return [OCMArg checkWithBlock:^BOOL(
id value) {
154 if (![value isKindOfClass:[NSEvent
class]]) {
157 NSEvent*
event = value;
158 return event.keyCode == keyCode;
163 void clearEvents(std::vector<FlutterKeyEvent>& events) {
164 for (FlutterKeyEvent& event : events) {
165 if (event.character !=
nullptr) {
172 #define VERIFY_DOWN(OUT_LOGICAL, OUT_CHAR) \
173 EXPECT_EQ(events[0].type, kFlutterKeyEventTypeDown); \
174 EXPECT_EQ(events[0].logical, static_cast<uint64_t>(OUT_LOGICAL)); \
175 EXPECT_STREQ(events[0].character, (OUT_CHAR)); \
181 - (nonnull instancetype)
init;
184 - (void)respondEmbedderCallsWith:(BOOL)response;
191 - (void)recordEmbedderEventsTo:(nonnull std::vector<FlutterKeyEvent>*)storage
192 returning:(
bool)handled;
195 - (void)respondChannelCallsWith:(BOOL)response;
203 - (void)respondTextInputWith:(BOOL)response;
212 - (void)recordCallTypesTo:(nonnull NSMutableArray<NSNumber*>*)typeStorage
213 forTypes:(uint32_t)typeMask;
217 - (void)sendKeyboardChannelMessage:(NSData* _Nullable)message;
222 #pragma mark - Private
224 - (void)handleEmbedderEvent:(const FlutterKeyEvent&)event
225 callback:(nullable FlutterKeyEventCallback)callback
226 userData:(nullable
void*)userData;
228 - (void)handleChannelMessage:(NSString*)channel
229 message:(NSData* _Nullable)message
232 - (BOOL)handleTextInputKeyEvent:(NSEvent*)event;
236 AsyncEmbedderCallbackHandler _embedderHandler;
251 - (nonnull instancetype)init {
257 _nextResponder = OCMClassMock([NSResponder
class]);
267 binaryReply:[OCMArg any]])
268 .andCall(
self,
@selector(handleChannelMessage:message:binaryReply:));
269 OCMStub([
_messengerMock setMessageHandlerOnChannel:
@"flutter/keyboard"
270 binaryMessageHandler:[OCMArg any]])
271 .andCall(
self,
@selector(setKeyboardChannelHandler:handler:));
272 OCMStub([
_messengerMock sendOnChannel:
@"flutter/keyboard" message:[OCMArg any]])
273 .andCall(
self,
@selector(handleKeyboardChannelMessage:message:));
275 OCMStub([viewDelegateMock nextResponder]).andReturn(_nextResponder);
276 OCMStub([viewDelegateMock onTextInputKeyEvent:[OCMArg any]])
277 .andCall(
self,
@selector(handleTextInputKeyEvent:));
278 OCMStub([viewDelegateMock getBinaryMessenger]).andReturn(
_messengerMock);
279 OCMStub([viewDelegateMock sendKeyEvent:FlutterKeyEvent {} callback:nil userData:nil])
280 .ignoringNonObjectArgs()
281 .andCall(
self,
@selector(handleEmbedderEvent:callback:userData:));
282 OCMStub([viewDelegateMock subscribeToKeyboardLayoutChange:[OCMArg any]])
283 .andCall(
self,
@selector(onSetKeyboardLayoutNotifier:));
284 OCMStub([viewDelegateMock lookUpLayoutForKeyCode:0 shift:
false])
285 .ignoringNonObjectArgs()
286 .andCall(
self,
@selector(lookUpLayoutForKeyCode:shift:));
292 - (id)lastKeyboardChannelResult {
296 - (void)respondEmbedderCallsWith:(BOOL)response {
304 [storage addObject:callback];
308 - (void)recordEmbedderEventsTo:(nonnull std::vector<FlutterKeyEvent>*)storage
309 returning:(
bool)handled {
311 FlutterKeyEvent newEvent = *event;
312 if (event->character !=
nullptr) {
313 size_t charLen = strlen(event->character);
314 char* newCharacter =
new char[charLen + 1];
315 strlcpy(newCharacter, event->character, charLen + 1);
316 newEvent.character = newCharacter;
318 storage->push_back(newEvent);
323 - (void)respondChannelCallsWith:(BOOL)response {
331 [storage addObject:callback];
335 - (void)respondTextInputWith:(BOOL)response {
341 - (void)recordCallTypesTo:(nonnull NSMutableArray<NSNumber*>*)typeStorage
342 forTypes:(uint32_t)typeMask {
347 - (void)sendKeyboardChannelMessage:(NSData* _Nullable)message {
348 [_messengerMock sendOnChannel:@"flutter/keyboard" message:message];
351 - (void)setLayout:(const MockLayoutData&)layout {
358 #pragma mark - Private
360 - (void)handleEmbedderEvent:(const FlutterKeyEvent&)event
361 callback:(nullable FlutterKeyEventCallback)callback
362 userData:(nullable
void*)userData {
364 [_typeStorage addObject:@(kEmbedderCall)];
366 if (callback !=
nullptr) {
367 _embedderHandler(&event, ^(BOOL handled) {
368 callback(handled, userData);
373 - (void)handleChannelMessage:(NSString*)channel
374 message:(NSData* _Nullable)message
377 [_typeStorage addObject:@(kChannelCall)];
380 NSDictionary* result = @{
381 @"handled" : @(handled),
384 callback(encodedKeyEvent);
388 - (void)handleKeyboardChannelMessage:(NSString*)channel message:(NSData* _Nullable)message {
394 - (BOOL)handleTextInputKeyEvent:(NSEvent*)event {
396 [_typeStorage addObject:@(kTextCall)];
405 - (LayoutClue)lookUpLayoutForKeyCode:(uint16_t)keyCode shift:(BOOL)shift {
406 uint32_t cluePair = (*_currentLayout)[(keyCode * 2) + (shift ? 1 : 0)];
407 const uint32_t kCharMask = 0xffff;
408 const uint32_t kDeadKeyMask = 0x10000;
409 return LayoutClue{cluePair & kCharMask, (cluePair & kDeadKeyMask) != 0};
430 TEST(FlutterKeyboardManagerUnittests, SinglePrimaryResponder) {
434 TEST(FlutterKeyboardManagerUnittests, DoublePrimaryResponder) {
438 TEST(FlutterKeyboardManagerUnittests, SingleFinalResponder) {
442 TEST(FlutterKeyboardManagerUnittests, EmptyNextResponder) {
446 TEST(FlutterKeyboardManagerUnittests, GetPressedState) {
450 TEST(FlutterKeyboardManagerUnittests, KeyboardChannelGetPressedState) {
454 TEST(FlutterKeyboardManagerUnittests, RacingConditionBetweenKeyAndText) {
458 TEST(FlutterKeyboardManagerUnittests, CorrectLogicalKeyForLayouts) {
468 NSMutableArray<FlutterAsyncKeyCallback>* embedderCallbacks =
469 [NSMutableArray<FlutterAsyncKeyCallback> array];
473 [tester.manager handleEvent:keyDownEvent(0x50)];
474 EXPECT_EQ([embedderCallbacks count], 1u);
475 embedderCallbacks[0](FALSE);
476 OCMVerify([tester.
nextResponder keyDown:checkKeyDownEvent(0x50)]);
477 [embedderCallbacks removeAllObjects];
480 [tester.manager handleEvent:keyUpEvent(0x50)];
481 EXPECT_EQ([embedderCallbacks count], 1u);
482 embedderCallbacks[0](TRUE);
494 [tester.manager handleEvent:keyDownEvent(0x50)];
496 NSMutableArray<FlutterAsyncKeyCallback>* embedderCallbacks =
497 [NSMutableArray<FlutterAsyncKeyCallback> array];
498 NSMutableArray<FlutterAsyncKeyCallback>* channelCallbacks =
499 [NSMutableArray<FlutterAsyncKeyCallback> array];
504 [tester.manager handleEvent:keyUpEvent(0x50)];
505 EXPECT_EQ([embedderCallbacks count], 1u);
506 EXPECT_EQ([channelCallbacks count], 1u);
507 embedderCallbacks[0](TRUE);
508 channelCallbacks[0](TRUE);
509 EXPECT_EQ([embedderCallbacks count], 1u);
510 EXPECT_EQ([channelCallbacks count], 1u);
512 [embedderCallbacks removeAllObjects];
513 [channelCallbacks removeAllObjects];
518 [tester.manager handleEvent:keyDownEvent(0x50)];
522 [tester.manager handleEvent:keyUpEvent(0x50)];
523 EXPECT_EQ([embedderCallbacks count], 1u);
524 EXPECT_EQ([channelCallbacks count], 1u);
525 embedderCallbacks[0](FALSE);
526 channelCallbacks[0](TRUE);
527 EXPECT_EQ([embedderCallbacks count], 1u);
528 EXPECT_EQ([channelCallbacks count], 1u);
530 [embedderCallbacks removeAllObjects];
531 [channelCallbacks removeAllObjects];
534 [tester.manager handleEvent:keyDownEvent(0x53)];
535 EXPECT_EQ([embedderCallbacks count], 1u);
536 EXPECT_EQ([channelCallbacks count], 1u);
537 embedderCallbacks[0](FALSE);
538 channelCallbacks[0](FALSE);
539 EXPECT_EQ([embedderCallbacks count], 1u);
540 EXPECT_EQ([channelCallbacks count], 1u);
541 OCMVerify([tester.
nextResponder keyDown:checkKeyDownEvent(0x53)]);
542 [embedderCallbacks removeAllObjects];
543 [channelCallbacks removeAllObjects];
554 [tester.manager handleEvent:keyDownEvent(0x50)];
556 NSMutableArray<FlutterAsyncKeyCallback>* callbacks =
557 [NSMutableArray<FlutterAsyncKeyCallback> array];
563 [tester.manager handleEvent:keyUpEvent(0x50)];
564 EXPECT_EQ([callbacks count], 1u);
567 [callbacks removeAllObjects];
571 [tester.manager handleEvent:keyDownEvent(0x50)];
577 [tester.manager handleEvent:keyUpEvent(0x50)];
578 EXPECT_EQ([callbacks count], 1u);
581 [callbacks removeAllObjects];
585 [tester.manager handleEvent:keyDownEvent(0x50)];
586 EXPECT_EQ([callbacks count], 1u);
588 OCMVerify([tester.
nextResponder keyDown:checkKeyDownEvent(0x50)]);
589 [callbacks removeAllObjects];
601 [tester.manager handleEvent:keyDownEvent(0x50)];
613 [tester.manager handleEvent:keyDownEvent(kVK_ANSI_A)];
615 NSDictionary* pressingRecords = [tester.manager getPressedState];
616 EXPECT_EQ([pressingRecords count], 1u);
617 EXPECT_EQ(pressingRecords[@(kPhysicalKeyA)], @(kLogicalKeyA));
628 [tester.manager handleEvent:keyDownEvent(kVK_ANSI_A)];
632 NSData* getKeyboardStateMessage =
639 EXPECT_EQ([decoded count], 1u);
640 EXPECT_EQ(decoded[@(kPhysicalKeyA)], @(kLogicalKeyA));
653 NSMutableArray<FlutterAsyncKeyCallback>* keyCallbacks =
654 [NSMutableArray<FlutterAsyncKeyCallback> array];
657 NSMutableArray<NSNumber*>* allCalls = [NSMutableArray<NSNumber*> array];
662 [tester.manager handleEvent:keyDownEvent(kKeyCodeEmpty, @"Æ °", @"Æ °")];
663 EXPECT_EQ([keyCallbacks count], 1u);
664 EXPECT_EQ([allCalls count], 1u);
665 EXPECT_EQ(allCalls[0], @(kEmbedderCall));
666 keyCallbacks[0](
false);
667 EXPECT_EQ([keyCallbacks count], 1u);
668 EXPECT_EQ([allCalls count], 2u);
669 EXPECT_EQ(allCalls[1], @(kTextCall));
670 [keyCallbacks removeAllObjects];
671 [allCalls removeAllObjects];
673 [tester.manager handleEvent:keyUpEvent(kKeyCodeEmpty)];
674 EXPECT_EQ([keyCallbacks count], 1u);
675 keyCallbacks[0](
false);
676 EXPECT_EQ([keyCallbacks count], 1u);
677 EXPECT_EQ([allCalls count], 2u);
678 [keyCallbacks removeAllObjects];
679 [allCalls removeAllObjects];
684 [tester.manager handleEvent:keyDownEvent(kVK_ANSI_O, @"o", @"o")];
685 [tester.manager handleEvent:keyUpEvent(kVK_ANSI_O)];
686 EXPECT_EQ([keyCallbacks count], 1u);
687 EXPECT_EQ([allCalls count], 1u);
688 EXPECT_EQ(allCalls[0], @(kEmbedderCall));
693 [tester.manager handleEvent:keyDownEvent(kVK_Delete)];
694 [tester.manager handleEvent:keyUpEvent(kVK_Delete)];
695 EXPECT_EQ([keyCallbacks count], 1u);
696 EXPECT_EQ([allCalls count], 1u);
700 keyCallbacks[0](
false);
701 EXPECT_EQ([keyCallbacks count], 2u);
702 EXPECT_EQ([allCalls count], 3u);
703 EXPECT_EQ(allCalls[1], @(kTextCall));
704 EXPECT_EQ(allCalls[2], @(kEmbedderCall));
708 keyCallbacks[1](
false);
709 EXPECT_EQ([keyCallbacks count], 3u);
710 EXPECT_EQ([allCalls count], 5u);
711 EXPECT_EQ(allCalls[3], @(kTextCall));
712 EXPECT_EQ(allCalls[4], @(kEmbedderCall));
715 keyCallbacks[2](
false);
716 keyCallbacks[3](
false);
725 std::vector<FlutterKeyEvent> events;
730 auto sendTap = [&](uint16_t keyCode, NSString* chars, NSString* charsUnmod) {
731 [tester.manager handleEvent:keyDownEvent(keyCode, chars, charsUnmod)];
732 [tester.manager handleEvent:keyUpEvent(keyCode)];
737 sendTap(kVK_ANSI_A,
@"a",
@"a");
740 sendTap(kVK_ANSI_A,
@"A",
@"A");
743 sendTap(kVK_ANSI_A,
@"Ã¥",
@"a");
746 sendTap(kVK_ANSI_T,
@"t",
@"t");
749 sendTap(kVK_ANSI_1,
@"1",
@"1");
752 sendTap(kVK_ANSI_1,
@"!",
@"!");
755 sendTap(kVK_ANSI_Minus,
@"-",
@"-");
758 sendTap(kVK_ANSI_Minus,
@"=",
@"=");
762 [tester setLayout:kFrenchLayout];
764 sendTap(kVK_ANSI_A,
@"q",
@"q");
767 sendTap(kVK_ANSI_A,
@"Q",
@"Q");
770 sendTap(kVK_ANSI_Semicolon,
@"m",
@"m");
773 sendTap(kVK_ANSI_M,
@",",
@",");
776 sendTap(kVK_ANSI_1,
@"&",
@"&");
779 sendTap(kVK_ANSI_1,
@"1",
@"1");
782 sendTap(kVK_ANSI_Minus,
@")",
@")");
785 sendTap(kVK_ANSI_Minus,
@"°",
@"°");
789 [tester setLayout:kRussianLayout];
791 sendTap(kVK_ANSI_A,
@"Ñ„",
@"Ñ„");
794 sendTap(kVK_ANSI_1,
@"1",
@"1");
797 sendTap(kVK_ANSI_LeftBracket,
@"Ñ…",
@"Ñ…");
802 [tester setLayout:kKhmerLayout];
804 sendTap(kVK_ANSI_2,
@"២",
@"២");