180 : NSObject
181- (nonnull instancetype)init;
182
183
184- (void)respondEmbedderCallsWith:(
BOOL)response;
185
186
187
188
190
192 returning:(bool)handled;
193
194
195- (void)respondChannelCallsWith:(
BOOL)response;
196
197
198
199
201
202
203- (void)respondTextInputWith:(
BOOL)response;
204
205
206
207
208
209
210
211
212- (void)recordCallTypesTo:(nonnull NSMutableArray<NSNumber*>*)typeStorage
213 forTypes:(uint32_t)typeMask;
214
215- (
id)lastKeyboardChannelResult;
216
217- (void)sendKeyboardChannelMessage:(NSData* _Nullable)
message;
218
220@property(readonly, nonatomic, strong) id<FlutterKeyboardManagerEventContext> eventContextMock;
221@property(nonatomic, nullable, strong) NSResponder* nextResponder;
222
223#pragma mark - Private
224
227 userData:(nullable void*)userData;
228
229- (void)handleChannelMessage:(NSString*)
channel
232
233- (
BOOL)handleTextInputKeyEvent:(NSEvent*)event;
234@end
235
237 AsyncEmbedderCallbackHandler _embedderHandler;
240
243
246
250
252}
253
254- (nonnull instancetype)init {
255 self = [super init];
256 if (self == nil) {
257 return nil;
258 }
259
260 _nextResponder = OCMClassMock([NSResponder class]);
261 [self respondChannelCallsWith:FALSE];
262 [self respondEmbedderCallsWith:FALSE];
263 [self respondTextInputWith:FALSE];
264
266
267 _messengerMock = OCMStrictProtocolMock(
@protocol(FlutterBinaryMessenger));
270 binaryReply:[OCMArg any]])
271 .andCall(
self, @selector(handleChannelMessage:
message:binaryReply:));
272 OCMStub([
_messengerMock setMessageHandlerOnChannel:
@"flutter/keyboard"
273 binaryMessageHandler:[OCMArg any]])
274 .andCall(
self, @selector(setKeyboardChannelHandler:
handler:));
276 .andCall(
self, @selector(handleKeyboardChannelMessage:
message:));
277 id managerDelegateMock = OCMStrictProtocolMock(@protocol(FlutterKeyboardManagerDelegate));
278 OCMStub([managerDelegateMock binaryMessenger]).andReturn(
_messengerMock);
279 OCMStub([managerDelegateMock sendKeyEvent:*(const
FlutterKeyEvent*)[OCMArg anyPointer]
281 userData:nil])
282 .ignoringNonObjectArgs()
283 .andCall(
self, @selector(handleEmbedderEvent:
callback:userData:));
284
285 _eventContextMock = OCMStrictProtocolMock(
@protocol(FlutterKeyboardManagerEventContext));
288 .andCall(
self, @selector(handleTextInputKeyEvent:));
289
291 OCMStub([keyboardLayoutMock lookUpLayoutForKeyCode:0 shift:false])
292 .ignoringNonObjectArgs()
293 .andCall(
self, @selector(lookUpLayoutForKeyCode:shift:));
294 OCMStub([keyboardLayoutMock setDelegate:[OCMArg any]])
295 .andCall(
self, @selector(onSetKeyboardLayoutDelegate:));
296
298 keyboardLayout:keyboardLayoutMock];
299 return self;
300}
301
302- (
id)lastKeyboardChannelResult {
304}
305
306- (void)respondEmbedderCallsWith:(
BOOL)response {
309 };
310}
311
314 [storage addObject:callback];
315 };
316}
317
319 returning:(bool)handled {
323 size_t charLen = strlen(event->
character);
324 char* newCharacter = new char[charLen + 1];
325 strlcpy(newCharacter, event->
character, charLen + 1);
327 }
328 storage->push_back(newEvent);
330 };
331}
332
333- (void)respondChannelCallsWith:(
BOOL)response {
336 };
337}
338
341 [storage addObject:callback];
342 };
343}
344
345- (void)respondTextInputWith:(
BOOL)response {
347 return response;
348 };
349}
350
351- (void)recordCallTypesTo:(nonnull NSMutableArray<NSNumber*>*)typeStorage
352 forTypes:(uint32_t)typeMask {
355}
356
357- (void)sendKeyboardChannelMessage:(NSData* _Nullable)message {
358 [_messengerMock sendOnChannel:@"flutter/keyboard" message:message];
359}
360
361- (void)setLayout:(const MockLayoutData&)layout {
363 [_keyboardLayoutDelegate keyboardLayoutDidChange];
364}
365
366#pragma mark - Private
367
370 userData:(nullable void*)userData {
372 [_typeStorage addObject:@(kEmbedderCall)];
373 }
375 _embedderHandler(&event, ^(
BOOL handled) {
377 });
378 }
379}
380
381- (void)handleChannelMessage:(NSString*)channel
382 message:(NSData* _Nullable)message
385 [_typeStorage addObject:@(kChannelCall)];
386 }
388 NSDictionary* result = @{
389 @"handled" : @(handled),
390 };
393 });
394}
395
396- (void)handleKeyboardChannelMessage:(NSString*)channel message:(NSData* _Nullable)message {
399 });
400}
401
402- (
BOOL)handleTextInputKeyEvent:(NSEvent*)event {
404 [_typeStorage addObject:@(kTextCall)];
405 }
407}
408
409- (void)onSetKeyboardLayoutDelegate:(id<FlutterKeyboardLayoutDelegate>)delegate {
411}
412
413- (LayoutClue)lookUpLayoutForKeyCode:(uint16_t)keyCode shift:(
BOOL)shift {
414 uint32_t cluePair = (*_currentLayout)[(keyCode * 2) + (shift ? 1 : 0)];
415 const uint32_t kCharMask = 0xffff;
416 const uint32_t kDeadKeyMask = 0x10000;
417 return LayoutClue{cluePair & kCharMask, (cluePair & kDeadKeyMask) != 0};
418}
419
422}
423
424@end
425
436@end
437
439
440TEST(FlutterKeyboardManagerUnittests, SinglePrimaryResponder) {
442}
443
444TEST(FlutterKeyboardManagerUnittests, DoublePrimaryResponder) {
446}
447
448TEST(FlutterKeyboardManagerUnittests, SingleFinalResponder) {
450}
451
452TEST(FlutterKeyboardManagerUnittests, EmptyNextResponder) {
454}
455
456TEST(FlutterKeyboardManagerUnittests, GetPressedState) {
458}
459
460TEST(FlutterKeyboardManagerUnittests, KeyboardChannelGetPressedState) {
462}
463
464TEST(FlutterKeyboardManagerUnittests, RacingConditionBetweenKeyAndText) {
466}
467
468TEST(FlutterKeyboardManagerUnittests, CorrectLogicalKeyForLayouts) {
470}
471
472TEST(FlutterKeyboardManagerUnittests, ShouldNotHoldStrongReferenceToDelegate) {
474}
475
476}
477
479
482 NSMutableArray<FlutterAsyncKeyCallback>* embedderCallbacks =
483 [NSMutableArray<FlutterAsyncKeyCallback> array];
485
486
487 [tester.manager handleEvent:keyDownEvent(0x50) withContext:tester.eventContextMock];
488 EXPECT_EQ([embedderCallbacks count], 1u);
489 embedderCallbacks[0](FALSE);
490 OCMVerify([tester.
nextResponder keyDown:checkKeyDownEvent(0x50)]);
491 [embedderCallbacks removeAllObjects];
492
493
494 [tester.manager handleEvent:keyUpEvent(0x50) withContext:tester.eventContextMock];
495 EXPECT_EQ([embedderCallbacks count], 1u);
496 embedderCallbacks[0](
TRUE);
497
498
499 return true;
500}
501
504
505
508 [tester.manager handleEvent:keyDownEvent(0x50) withContext:tester.eventContextMock];
509
510 NSMutableArray<FlutterAsyncKeyCallback>* embedderCallbacks =
511 [NSMutableArray<FlutterAsyncKeyCallback> array];
512 NSMutableArray<FlutterAsyncKeyCallback>* channelCallbacks =
513 [NSMutableArray<FlutterAsyncKeyCallback> array];
516
517
518 [tester.manager handleEvent:keyUpEvent(0x50) withContext:tester.eventContextMock];
519 EXPECT_EQ([embedderCallbacks count], 1u);
520 EXPECT_EQ([channelCallbacks count], 1u);
521 embedderCallbacks[0](
TRUE);
522 channelCallbacks[0](
TRUE);
523 EXPECT_EQ([embedderCallbacks count], 1u);
524 EXPECT_EQ([channelCallbacks count], 1u);
525
526 [embedderCallbacks removeAllObjects];
527 [channelCallbacks removeAllObjects];
528
529
532 [tester.manager handleEvent:keyDownEvent(0x50) withContext:tester.eventContextMock];
533
536 [tester.manager handleEvent:keyUpEvent(0x50) withContext:tester.eventContextMock];
537 EXPECT_EQ([embedderCallbacks count], 1u);
538 EXPECT_EQ([channelCallbacks count], 1u);
539 embedderCallbacks[0](FALSE);
540 channelCallbacks[0](
TRUE);
541 EXPECT_EQ([embedderCallbacks count], 1u);
542 EXPECT_EQ([channelCallbacks count], 1u);
543
544 [embedderCallbacks removeAllObjects];
545 [channelCallbacks removeAllObjects];
546
547
548 [tester.manager handleEvent:keyDownEvent(0x53) withContext:tester.eventContextMock];
549 EXPECT_EQ([embedderCallbacks count], 1u);
550 EXPECT_EQ([channelCallbacks count], 1u);
551 embedderCallbacks[0](FALSE);
552 channelCallbacks[0](FALSE);
553 EXPECT_EQ([embedderCallbacks count], 1u);
554 EXPECT_EQ([channelCallbacks count], 1u);
555 OCMVerify([tester.
nextResponder keyDown:checkKeyDownEvent(0x53)]);
556 [embedderCallbacks removeAllObjects];
557 [channelCallbacks removeAllObjects];
558
559 return true;
560}
561
564
565
568 [tester.manager handleEvent:keyDownEvent(0x50) withContext:tester.eventContextMock];
569
570 NSMutableArray<FlutterAsyncKeyCallback>* callbacks =
571 [NSMutableArray<FlutterAsyncKeyCallback> array];
573
574
575
577 [tester.manager handleEvent:keyUpEvent(0x50) withContext:tester.eventContextMock];
578 EXPECT_EQ([callbacks count], 1u);
580
581 [callbacks removeAllObjects];
582
583
585 [tester.manager handleEvent:keyDownEvent(0x50) withContext:tester.eventContextMock];
586
587
588
591 [tester.manager handleEvent:keyUpEvent(0x50) withContext:tester.eventContextMock];
592 EXPECT_EQ([callbacks count], 1u);
593 callbacks[0](FALSE);
594
595 [callbacks removeAllObjects];
596
597
599 [tester.manager handleEvent:keyDownEvent(0x50) withContext:tester.eventContextMock];
600 EXPECT_EQ([callbacks count], 1u);
601 callbacks[0](FALSE);
602 OCMVerify([tester.
nextResponder keyDown:checkKeyDownEvent(0x50)]);
603 [callbacks removeAllObjects];
604
605 return true;
606}
607
611
615 [tester.manager handleEvent:keyDownEvent(0x50) withContext:tester.eventContextMock];
616
617
618 return true;
619}
620
623
627 [tester.manager handleEvent:keyDownEvent(kVK_ANSI_A) withContext:tester.eventContextMock];
628
629 NSDictionary* pressingRecords = [tester.manager getPressedState];
630 EXPECT_EQ([pressingRecords count], 1u);
631 EXPECT_EQ(pressingRecords[@(kPhysicalKeyA)], @(kLogicalKeyA));
632
633 return true;
634}
635
638
642 [tester.manager handleEvent:keyDownEvent(kVK_ANSI_A) withContext:tester.eventContextMock];
643
646 NSData* getKeyboardStateMessage =
649
652
653 EXPECT_EQ([decoded count], 1u);
654 EXPECT_EQ(decoded[@(kPhysicalKeyA)], @(kLogicalKeyA));
655
656 return true;
657}
658
659
662
663
664
665
666
667 NSMutableArray<FlutterAsyncKeyCallback>* keyCallbacks =
668 [NSMutableArray<FlutterAsyncKeyCallback> array];
670
671 NSMutableArray<NSNumber*>* allCalls = [NSMutableArray<NSNumber*> array];
673
674
675
676 [tester.manager handleEvent:keyDownEvent(kKeyCodeEmpty, @"ư", @"ư")
677 withContext:tester.eventContextMock];
678 EXPECT_EQ([keyCallbacks count], 1u);
679 EXPECT_EQ([allCalls count], 1u);
680 EXPECT_EQ(allCalls[0], @(kEmbedderCall));
681 keyCallbacks[0](false);
682 EXPECT_EQ([keyCallbacks count], 1u);
683 EXPECT_EQ([allCalls count], 2u);
684 EXPECT_EQ(allCalls[1], @(kTextCall));
685 [keyCallbacks removeAllObjects];
686 [allCalls removeAllObjects];
687
688 [tester.manager handleEvent:keyUpEvent(kKeyCodeEmpty) withContext:tester.eventContextMock];
689 EXPECT_EQ([keyCallbacks count], 1u);
690 keyCallbacks[0](false);
691 EXPECT_EQ([keyCallbacks count], 1u);
692 EXPECT_EQ([allCalls count], 2u);
693 [keyCallbacks removeAllObjects];
694 [allCalls removeAllObjects];
695
696
697
698
699 [tester.manager handleEvent:keyDownEvent(kVK_ANSI_O, @"o", @"o")
700 withContext:tester.eventContextMock];
701 [tester.manager handleEvent:keyUpEvent(kVK_ANSI_O) withContext:tester.eventContextMock];
702 EXPECT_EQ([keyCallbacks count], 1u);
703 EXPECT_EQ([allCalls count], 1u);
704 EXPECT_EQ(allCalls[0], @(kEmbedderCall));
705
706
707
708
709 [tester.manager handleEvent:keyDownEvent(kVK_Delete) withContext:tester.eventContextMock];
710 [tester.manager handleEvent:keyUpEvent(kVK_Delete) withContext:tester.eventContextMock];
711 EXPECT_EQ([keyCallbacks count], 1u);
712 EXPECT_EQ([allCalls count], 1u);
713
714
715
716 keyCallbacks[0](false);
717 EXPECT_EQ([keyCallbacks count], 2u);
718 EXPECT_EQ([allCalls count], 3u);
719 EXPECT_EQ(allCalls[1], @(kTextCall));
720 EXPECT_EQ(allCalls[2], @(kEmbedderCall));
721
722
723
724 keyCallbacks[1](false);
725 EXPECT_EQ([keyCallbacks count], 3u);
726 EXPECT_EQ([allCalls count], 5u);
727 EXPECT_EQ(allCalls[3], @(kTextCall));
728 EXPECT_EQ(allCalls[4], @(kEmbedderCall));
729
730
731 keyCallbacks[2](false);
732 keyCallbacks[3](false);
733
734 return true;
735}
736
740
741 std::vector<FlutterKeyEvent> events;
745
746 auto sendTap = [&](uint16_t keyCode, NSString* chars, NSString* charsUnmod) {
747 [tester.manager handleEvent:keyDownEvent(keyCode, chars, charsUnmod)
748 withContext:tester.eventContextMock];
749 [tester.manager handleEvent:keyUpEvent(keyCode) withContext:tester.eventContextMock];
750 };
751
752
753
754 sendTap(kVK_ANSI_A, @"a", @"a");
756
757 sendTap(kVK_ANSI_A, @"A", @"A");
759
760 sendTap(kVK_ANSI_A, @"å", @"a");
762
763 sendTap(kVK_ANSI_T, @"t", @"t");
765
766 sendTap(kVK_ANSI_1, @"1", @"1");
768
769 sendTap(kVK_ANSI_1, @"!", @"!");
771
772 sendTap(kVK_ANSI_Minus, @"-", @"-");
774
775 sendTap(kVK_ANSI_Minus, @"=", @"=");
777
778
779 [tester setLayout:kFrenchLayout];
780
781 sendTap(kVK_ANSI_A, @"q", @"q");
783
784 sendTap(kVK_ANSI_A, @"Q", @"Q");
786
787 sendTap(kVK_ANSI_Semicolon, @"m", @"m");
789
790 sendTap(kVK_ANSI_M, @",", @",");
792
793 sendTap(kVK_ANSI_1, @"&", @"&");
795
796 sendTap(kVK_ANSI_1, @"1", @"1");
798
799 sendTap(kVK_ANSI_Minus, @")", @")");
801
802 sendTap(kVK_ANSI_Minus, @"°", @"°");
804
805
806 [tester setLayout:kRussianLayout];
807
808 sendTap(kVK_ANSI_A, @"ф", @"ф");
810
811 sendTap(kVK_ANSI_1, @"1", @"1");
813
814 sendTap(kVK_ANSI_LeftBracket, @"х", @"х");
816
817
818
819 [tester setLayout:kKhmerLayout];
820
821 sendTap(kVK_ANSI_2, @"២", @"២");
823
825}
826
829 __weak id weakDelegate;
830
831 @autoreleasepool {
832 id binaryMessengerMock = OCMStrictProtocolMock(@protocol(FlutterBinaryMessenger));
833 OCMStub([binaryMessengerMock setMessageHandlerOnChannel:[OCMArg any]
834 binaryMessageHandler:[OCMArg any]]);
835
836 id delegateMock = OCMStrictProtocolMock(@protocol(FlutterKeyboardManagerDelegate));
837 OCMStub([delegateMock binaryMessenger]).andReturn(binaryMessengerMock);
838
841 strongKeyboardManager = keyboardManager;
842 weakDelegate = delegateMock;
843 }
844
845 return weakDelegate == nil;
846}
847
848@end
NS_ASSUME_NONNULL_BEGIN typedef void(^ FlutterBinaryReply)(NSData *_Nullable reply)
void(^ FlutterBinaryMessageHandler)(NSData *_Nullable message, FlutterBinaryReply reply)
void(* FlutterKeyEventCallback)(bool, void *)
const gchar FlBinaryMessengerMessageHandler handler
G_BEGIN_DECLS GBytes * message
FlutterDesktopBinaryReply callback
bool shouldNotHoldStrongReferenceToDelegate()
bool doublePrimaryResponder()
bool racingConditionBetweenKeyAndText()
bool keyboardChannelGetPressedState()
bool emptyNextResponder()
bool correctLogicalKeyForLayouts()
bool singlePrimaryResponder()
instancetype methodCallWithMethodName:arguments:(NSString *method,[arguments] id _Nullable arguments)
void respondChannelCallsWith:(BOOL response)
void recordChannelCallsTo:(nonnull NSMutableArray< FlutterAsyncKeyCallback > *storage)
id lastKeyboardChannelResult()
void recordEmbedderEventsTo:returning:(nonnull std::vector< FlutterKeyEvent > *storage,[returning] bool handled)
NSResponder * nextResponder
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
NSObject< FlutterKeyboardManagerEventContext > * _eventContextMock
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
__weak id< FlutterKeyboardLayoutDelegate > _keyboardLayoutDelegate
TEST(NativeAssetsManagerTest, NoAvailableAssets)
instancetype sharedInstance()