179 : NSObject
180- (nonnull instancetype)init;
181
182
183- (void)respondEmbedderCallsWith:(
BOOL)response;
184
185
186
187
189
191 returning:(bool)handled;
192
193
194- (void)respondChannelCallsWith:(
BOOL)response;
195
196
197
198
200
201
202- (void)respondTextInputWith:(
BOOL)response;
203
204
205
206
207
208
209
210
211- (void)recordCallTypesTo:(nonnull NSMutableArray<NSNumber*>*)typeStorage
212 forTypes:(uint32_t)typeMask;
213
214- (
id)lastKeyboardChannelResult;
215
216- (void)sendKeyboardChannelMessage:(NSData* _Nullable)
message;
217
219@property(nonatomic, nullable, strong) NSResponder* nextResponder;
220
221#pragma mark - Private
222
225 userData:(nullable void*)userData;
226
227- (void)handleChannelMessage:(NSString*)channel
230
231- (
BOOL)handleTextInputKeyEvent:(NSEvent*)
event;
232@end
233
235 AsyncEmbedderCallbackHandler _embedderHandler;
238
241
244
248}
249
250- (nonnull instancetype)
init {
251 self = [super init];
252 if (self == nil) {
253 return nil;
254 }
255
256 _nextResponder = OCMClassMock([NSResponder class]);
257 [self respondChannelCallsWith:FALSE];
258 [self respondEmbedderCallsWith:FALSE];
259 [self respondTextInputWith:FALSE];
260
262
263 _messengerMock = OCMStrictProtocolMock(
@protocol(FlutterBinaryMessenger));
266 binaryReply:[OCMArg
any]])
267 .andCall(
self, @selector(handleChannelMessage:
message:binaryReply:));
268 OCMStub([
_messengerMock setMessageHandlerOnChannel:
@"flutter/keyboard"
269 binaryMessageHandler:[OCMArg
any]])
270 .andCall(
self, @selector(setKeyboardChannelHandler:handler:));
272 .andCall(
self, @selector(handleKeyboardChannelMessage:
message:));
273 id viewDelegateMock = OCMStrictProtocolMock(@protocol(FlutterKeyboardViewDelegate));
274 OCMStub([viewDelegateMock nextResponder]).andReturn(_nextResponder);
275 OCMStub([viewDelegateMock onTextInputKeyEvent:[OCMArg
any]])
276 .andCall(
self, @selector(handleTextInputKeyEvent:));
277 OCMStub([viewDelegateMock getBinaryMessenger]).andReturn(
_messengerMock);
278 OCMStub([viewDelegateMock sendKeyEvent:*(const
FlutterKeyEvent*)[OCMArg anyPointer]
280 userData:nil])
281 .ignoringNonObjectArgs()
282 .andCall(
self, @selector(handleEmbedderEvent:
callback:userData:));
283 OCMStub([viewDelegateMock subscribeToKeyboardLayoutChange:[OCMArg
any]])
284 .andCall(
self, @selector(onSetKeyboardLayoutNotifier:));
285 OCMStub([viewDelegateMock lookUpLayoutForKeyCode:0 shift:
false])
286 .ignoringNonObjectArgs()
287 .andCall(
self, @selector(lookUpLayoutForKeyCode:shift:));
288
290 return self;
291}
292
293- (
id)lastKeyboardChannelResult {
295}
296
297- (void)respondEmbedderCallsWith:(
BOOL)response {
300 };
301}
302
305 [storage addObject:callback];
306 };
307}
308
310 returning:(bool)handled {
313 if (
event->character !=
nullptr) {
314 size_t charLen = strlen(
event->character);
315 char* newCharacter = new char[charLen + 1];
316 strlcpy(newCharacter,
event->character, charLen + 1);
318 }
319 storage->push_back(newEvent);
321 };
322}
323
324- (void)respondChannelCallsWith:(
BOOL)response {
327 };
328}
329
332 [storage addObject:callback];
333 };
334}
335
336- (void)respondTextInputWith:(
BOOL)response {
338 return response;
339 };
340}
341
342- (void)recordCallTypesTo:(nonnull NSMutableArray<NSNumber*>*)typeStorage
343 forTypes:(uint32_t)typeMask {
346}
347
348- (void)sendKeyboardChannelMessage:(NSData* _Nullable)message {
349 [_messengerMock sendOnChannel:@"flutter/keyboard" message:message];
350}
351
352- (void)setLayout:(const MockLayoutData&)layout {
356 }
357}
358
359#pragma mark - Private
360
363 userData:(nullable void*)userData {
365 [_typeStorage addObject:@(kEmbedderCall)];
366 }
368 _embedderHandler(&
event, ^(
BOOL handled) {
370 });
371 }
372}
373
374- (void)handleChannelMessage:(NSString*)channel
375 message:(NSData* _Nullable)message
378 [_typeStorage addObject:@(kChannelCall)];
379 }
382 @"handled" : @(handled),
383 };
386 });
387}
388
389- (void)handleKeyboardChannelMessage:(NSString*)channel message:(NSData* _Nullable)message {
392 });
393}
394
395- (
BOOL)handleTextInputKeyEvent:(NSEvent*)event {
397 [_typeStorage addObject:@(kTextCall)];
398 }
400}
401
404}
405
406- (LayoutClue)lookUpLayoutForKeyCode:(uint16_t)keyCode shift:(
BOOL)shift {
407 uint32_t cluePair = (*_currentLayout)[(keyCode * 2) + (shift ? 1 : 0)];
408 const uint32_t kCharMask = 0xffff;
409 const uint32_t kDeadKeyMask = 0x10000;
410 return LayoutClue{cluePair & kCharMask, (cluePair & kDeadKeyMask) != 0};
411}
412
415}
416
417@end
418
429@end
430
432
433TEST(FlutterKeyboardManagerUnittests, SinglePrimaryResponder) {
435}
436
437TEST(FlutterKeyboardManagerUnittests, DoublePrimaryResponder) {
439}
440
441TEST(FlutterKeyboardManagerUnittests, SingleFinalResponder) {
443}
444
445TEST(FlutterKeyboardManagerUnittests, EmptyNextResponder) {
447}
448
449TEST(FlutterKeyboardManagerUnittests, GetPressedState) {
451}
452
453TEST(FlutterKeyboardManagerUnittests, KeyboardChannelGetPressedState) {
455}
456
457TEST(FlutterKeyboardManagerUnittests, RacingConditionBetweenKeyAndText) {
459}
460
461TEST(FlutterKeyboardManagerUnittests, CorrectLogicalKeyForLayouts) {
463}
464
465TEST(FlutterKeyboardManagerUnittests, ShouldNotHoldStrongReferenceToViewDelegate) {
466 ASSERT_TRUE(
468}
469
470}
471
473
476 NSMutableArray<FlutterAsyncKeyCallback>* embedderCallbacks =
477 [NSMutableArray<FlutterAsyncKeyCallback> array];
479
480
481 [tester.manager handleEvent:keyDownEvent(0x50)];
482 EXPECT_EQ([embedderCallbacks
count], 1u);
483 embedderCallbacks[0](
FALSE);
484 OCMVerify([
tester.nextResponder keyDown:checkKeyDownEvent(0x50)]);
485 [embedderCallbacks removeAllObjects];
486
487
488 [tester.manager handleEvent:keyUpEvent(0x50)];
489 EXPECT_EQ([embedderCallbacks
count], 1u);
490 embedderCallbacks[0](
TRUE);
491
492
493 return true;
494}
495
498
499
502 [tester.manager handleEvent:keyDownEvent(0x50)];
503
504 NSMutableArray<FlutterAsyncKeyCallback>* embedderCallbacks =
505 [NSMutableArray<FlutterAsyncKeyCallback> array];
506 NSMutableArray<FlutterAsyncKeyCallback>* channelCallbacks =
507 [NSMutableArray<FlutterAsyncKeyCallback> array];
510
511
512 [tester.manager handleEvent:keyUpEvent(0x50)];
513 EXPECT_EQ([embedderCallbacks
count], 1u);
514 EXPECT_EQ([channelCallbacks
count], 1u);
515 embedderCallbacks[0](
TRUE);
516 channelCallbacks[0](
TRUE);
517 EXPECT_EQ([embedderCallbacks
count], 1u);
518 EXPECT_EQ([channelCallbacks
count], 1u);
519
520 [embedderCallbacks removeAllObjects];
521 [channelCallbacks removeAllObjects];
522
523
526 [tester.manager handleEvent:keyDownEvent(0x50)];
527
530 [tester.manager handleEvent:keyUpEvent(0x50)];
531 EXPECT_EQ([embedderCallbacks
count], 1u);
532 EXPECT_EQ([channelCallbacks
count], 1u);
533 embedderCallbacks[0](
FALSE);
534 channelCallbacks[0](
TRUE);
535 EXPECT_EQ([embedderCallbacks
count], 1u);
536 EXPECT_EQ([channelCallbacks
count], 1u);
537
538 [embedderCallbacks removeAllObjects];
539 [channelCallbacks removeAllObjects];
540
541
542 [tester.manager handleEvent:keyDownEvent(0x53)];
543 EXPECT_EQ([embedderCallbacks
count], 1u);
544 EXPECT_EQ([channelCallbacks
count], 1u);
545 embedderCallbacks[0](
FALSE);
546 channelCallbacks[0](
FALSE);
547 EXPECT_EQ([embedderCallbacks
count], 1u);
548 EXPECT_EQ([channelCallbacks
count], 1u);
549 OCMVerify([
tester.nextResponder keyDown:checkKeyDownEvent(0x53)]);
550 [embedderCallbacks removeAllObjects];
551 [channelCallbacks removeAllObjects];
552
553 return true;
554}
555
558
559
562 [tester.manager handleEvent:keyDownEvent(0x50)];
563
564 NSMutableArray<FlutterAsyncKeyCallback>* callbacks =
565 [NSMutableArray<FlutterAsyncKeyCallback> array];
567
568
569
571 [tester.manager handleEvent:keyUpEvent(0x50)];
572 EXPECT_EQ([callbacks
count], 1u);
574
575 [callbacks removeAllObjects];
576
577
579 [tester.manager handleEvent:keyDownEvent(0x50)];
580
581
582
585 [tester.manager handleEvent:keyUpEvent(0x50)];
586 EXPECT_EQ([callbacks
count], 1u);
588
589 [callbacks removeAllObjects];
590
591
593 [tester.manager handleEvent:keyDownEvent(0x50)];
594 EXPECT_EQ([callbacks
count], 1u);
596 OCMVerify([
tester.nextResponder keyDown:checkKeyDownEvent(0x50)]);
597 [callbacks removeAllObjects];
598
599 return true;
600}
601
604 tester.nextResponder = nil;
605
609 [tester.manager handleEvent:keyDownEvent(0x50)];
610
611
612 return true;
613}
614
617
621 [tester.manager handleEvent:keyDownEvent(kVK_ANSI_A)];
622
623 NSDictionary* pressingRecords = [tester.manager getPressedState];
624 EXPECT_EQ([pressingRecords
count], 1u);
625 EXPECT_EQ(pressingRecords[@(kPhysicalKeyA)], @(kLogicalKeyA));
626
627 return true;
628}
629
632
636 [tester.manager handleEvent:keyDownEvent(kVK_ANSI_A)];
637
640 NSData* getKeyboardStateMessage =
643
646
647 EXPECT_EQ([decoded
count], 1u);
648 EXPECT_EQ(decoded[@(kPhysicalKeyA)], @(kLogicalKeyA));
649
650 return true;
651}
652
653
656
657
658
659
660
661 NSMutableArray<FlutterAsyncKeyCallback>* keyCallbacks =
662 [NSMutableArray<FlutterAsyncKeyCallback> array];
664
665 NSMutableArray<NSNumber*>* allCalls = [NSMutableArray<NSNumber*> array];
667
668
669
670 [tester.manager handleEvent:keyDownEvent(kKeyCodeEmpty, @"ư", @"ư")];
671 EXPECT_EQ([keyCallbacks
count], 1u);
672 EXPECT_EQ([allCalls
count], 1u);
673 EXPECT_EQ(allCalls[0], @(kEmbedderCall));
674 keyCallbacks[0](false);
675 EXPECT_EQ([keyCallbacks
count], 1u);
676 EXPECT_EQ([allCalls
count], 2u);
677 EXPECT_EQ(allCalls[1], @(kTextCall));
678 [keyCallbacks removeAllObjects];
679 [allCalls removeAllObjects];
680
681 [tester.manager handleEvent:keyUpEvent(kKeyCodeEmpty)];
682 EXPECT_EQ([keyCallbacks
count], 1u);
683 keyCallbacks[0](false);
684 EXPECT_EQ([keyCallbacks
count], 1u);
685 EXPECT_EQ([allCalls
count], 2u);
686 [keyCallbacks removeAllObjects];
687 [allCalls removeAllObjects];
688
689
690
691
692 [tester.manager handleEvent:keyDownEvent(kVK_ANSI_O, @"o", @"o")];
693 [tester.manager handleEvent:keyUpEvent(kVK_ANSI_O)];
694 EXPECT_EQ([keyCallbacks
count], 1u);
695 EXPECT_EQ([allCalls
count], 1u);
696 EXPECT_EQ(allCalls[0], @(kEmbedderCall));
697
698
699
700
701 [tester.manager handleEvent:keyDownEvent(kVK_Delete)];
702 [tester.manager handleEvent:keyUpEvent(kVK_Delete)];
703 EXPECT_EQ([keyCallbacks
count], 1u);
704 EXPECT_EQ([allCalls
count], 1u);
705
706
707
708 keyCallbacks[0](false);
709 EXPECT_EQ([keyCallbacks
count], 2u);
710 EXPECT_EQ([allCalls
count], 3u);
711 EXPECT_EQ(allCalls[1], @(kTextCall));
712 EXPECT_EQ(allCalls[2], @(kEmbedderCall));
713
714
715
716 keyCallbacks[1](false);
717 EXPECT_EQ([keyCallbacks
count], 3u);
718 EXPECT_EQ([allCalls
count], 5u);
719 EXPECT_EQ(allCalls[3], @(kTextCall));
720 EXPECT_EQ(allCalls[4], @(kEmbedderCall));
721
722
723 keyCallbacks[2](false);
724 keyCallbacks[3](false);
725
726 return true;
727}
728
731 tester.nextResponder = nil;
732
733 std::vector<FlutterKeyEvent> events;
737
738 auto sendTap = [&](uint16_t keyCode, NSString* chars, NSString* charsUnmod) {
739 [tester.manager handleEvent:keyDownEvent(keyCode, chars, charsUnmod)];
740 [tester.manager handleEvent:keyUpEvent(keyCode)];
741 };
742
743
744
745 sendTap(kVK_ANSI_A, @"a", @"a");
747
748 sendTap(kVK_ANSI_A, @"A", @"A");
750
751 sendTap(kVK_ANSI_A, @"å", @"a");
753
754 sendTap(kVK_ANSI_T, @"t", @"t");
756
757 sendTap(kVK_ANSI_1, @"1", @"1");
759
760 sendTap(kVK_ANSI_1, @"!", @"!");
762
763 sendTap(kVK_ANSI_Minus, @"-", @"-");
765
766 sendTap(kVK_ANSI_Minus, @"=", @"=");
768
769
771
772 sendTap(kVK_ANSI_A, @"q", @"q");
774
775 sendTap(kVK_ANSI_A, @"Q", @"Q");
777
778 sendTap(kVK_ANSI_Semicolon, @"m", @"m");
780
781 sendTap(kVK_ANSI_M, @",", @",");
783
784 sendTap(kVK_ANSI_1, @"&", @"&");
786
787 sendTap(kVK_ANSI_1, @"1", @"1");
789
790 sendTap(kVK_ANSI_Minus, @")", @")");
792
793 sendTap(kVK_ANSI_Minus, @"°", @"°");
795
796
798
799 sendTap(kVK_ANSI_A, @"ф", @"ф");
801
802 sendTap(kVK_ANSI_1, @"1", @"1");
804
805 sendTap(kVK_ANSI_LeftBracket, @"х", @"х");
807
808
809
811
812 sendTap(kVK_ANSI_2, @"២", @"២");
814
816}
817
820 __weak id weakViewDelegate;
821
822 @autoreleasepool {
823 id binaryMessengerMock = OCMStrictProtocolMock(@protocol(FlutterBinaryMessenger));
824 OCMStub([binaryMessengerMock setMessageHandlerOnChannel:[OCMArg
any]
825 binaryMessageHandler:[OCMArg
any]]);
826
827 id viewDelegateMock = OCMStrictProtocolMock(@protocol(FlutterKeyboardViewDelegate));
828 OCMStub([viewDelegateMock getBinaryMessenger]).andReturn(binaryMessengerMock);
829 OCMStub([viewDelegateMock subscribeToKeyboardLayoutChange:[OCMArg
any]]);
830
831 LayoutClue layoutClue;
832 OCMStub([viewDelegateMock lookUpLayoutForKeyCode:0 shift:NO])
833 .ignoringNonObjectArgs()
834 .andReturn(layoutClue);
837 strongKeyboardManager = keyboardManager;
838 weakViewDelegate = viewDelegateMock;
839 }
840
841 return weakViewDelegate == nil;
842}
843
844@end
#define TEST(S, s, D, expected)
NS_ASSUME_NONNULL_BEGIN typedef void(^ FlutterBinaryReply)(NSData *_Nullable reply)
void(^ FlutterBinaryMessageHandler)(NSData *_Nullable message, FlutterBinaryReply reply)
void(* FlutterKeyEventCallback)(bool, void *)
FlKeyEvent uint64_t FlKeyResponderAsyncCallback callback
std::function< void()> KeyboardLayoutNotifier
instancetype sharedInstance()
bool doublePrimaryResponder()
bool racingConditionBetweenKeyAndText()
bool keyboardChannelGetPressedState()
bool emptyNextResponder()
bool correctLogicalKeyForLayouts()
bool shouldNotHoldStrongReferenceToViewDelegate()
bool singlePrimaryResponder()
instancetype methodCallWithMethodName:arguments:(NSString *method,[arguments] id _Nullable arguments)
instancetype sharedInstance()
void respondChannelCallsWith:(BOOL response)
void recordChannelCallsTo:(nonnull NSMutableArray< FlutterAsyncKeyCallback > *storage)
id lastKeyboardChannelResult()
void setLayout:(const MockLayoutData &layout)
void recordEmbedderEventsTo:returning:(nonnull std::vector< FlutterKeyEvent > *storage,[returning] bool handled)
void sendKeyboardChannelMessage:(NSData *_Nullable message)
void respondEmbedderCallsWith:(BOOL response)
void respondTextInputWith:(BOOL response)
void recordCallTypesTo:forTypes:(nonnull NSMutableArray< NSNumber * > *typeStorage,[forTypes] uint32_t typeMask)
void recordEmbedderCallsTo:(nonnull NSMutableArray< FlutterAsyncKeyCallback > *storage)
void(^ FlutterAsyncKeyCallback)(BOOL handled)
FlutterTextInputPlugin * textInputPlugin
FlutterBinaryMessageHandler _keyboardHandler
uint32_t _typeStorageMask
id _keyboardChannelResult
AsyncKeyCallbackHandler _channelHandler
TextInputCallback _textCallback
#define VERIFY_DOWN(OUT_LOGICAL, OUT_CHAR)
NSMutableArray< NSNumber * > * _typeStorage
const MockLayoutData * _currentLayout
NSObject< FlutterBinaryMessenger > * _messengerMock
flutter::KeyboardLayoutNotifier _keyboardLayoutNotifier
void(^ KeyboardLayoutNotifier)()
SIT bool any(const Vec< 1, T > &x)