167NSEvent* CreateMouseEvent(NSEventModifierFlags modifierFlags) {
168 return [NSEvent mouseEventWithType:NSEventTypeMouseMoved
170 modifierFlags:modifierFlags
181#pragma mark - gtest tests
190 [viewControllerMock loadView];
191 auto subViews = [viewControllerMock.view subviews];
193 EXPECT_EQ([subViews count], 1u);
194 EXPECT_EQ(subViews[0], viewControllerMock.flutterView);
196 NSTextField* textField = [[NSTextField alloc] initWithFrame:NSMakeRect(0, 0, 1, 1)];
197 [viewControllerMock.view addSubview:textField];
199 subViews = [viewControllerMock.view subviews];
200 EXPECT_EQ([subViews count], 2u);
202 auto accessibilityChildren = viewControllerMock.
view.accessibilityChildren;
204 EXPECT_EQ([accessibilityChildren count], 1u);
205 EXPECT_EQ(accessibilityChildren[0], viewControllerMock.flutterView);
210 [viewControllerMock loadView];
211 EXPECT_EQ([viewControllerMock.flutterView acceptsFirstMouse:nil], YES);
219 [viewController loadView];
221 NSWindow*
window = [[NSWindow alloc] initWithContentRect:NSMakeRect(0, 0, 800, 600)
222 styleMask:NSBorderlessWindowMask
223 backing:NSBackingStoreBuffered
226 NSView* dummyView = [[NSView alloc] initWithFrame:CGRectZero];
227 [viewController.view addSubview:dummyView];
229 NSDictionary* setClientConfig = @{
237 [engine.textInputPlugin handleMethodCall:methodCall result:result];
240 [dummyView addSubview:engine.textInputPlugin];
243 EXPECT_TRUE([
window makeFirstResponder:
engine.textInputPlugin]);
244 EXPECT_EQ([
window firstResponder],
engine.textInputPlugin);
246 [viewController onAccessibilityStatusChanged:NO];
252 NSString* fixtures = @(testing::GetFixturesPath());
254 initWithAssetsPath:fixtures
255 ICUDataPath:[fixtures stringByAppendingString:@"/icudtl.dat"]];
262 id mockEngine = GetMockEngine();
267 id mockEngine = GetMockEngine();
273 id mockEngine = GetMockEngine();
279 id mockEngine = GetMockEngine();
284 id mockEngine = GetMockEngine();
286 testKeyEquivalentIsPassedToTextInputPlugin:mockEngine]);
290 id mockEngine = GetMockEngine();
292 testFlagsChangedEventsArePropagatedIfNotHandled:mockEngine]);
296 id mockEngine = GetMockEngine();
302 id mockEngine = GetMockEngine();
308 id mockEngine = GetMockEngine();
314 id mockEngine = GetMockEngine();
320 id mockEngine = GetMockEngine();
326 id mockEngine = GetMockEngine();
332 id mockEngine = GetMockEngine();
350#pragma mark - FlutterViewControllerTestObjC
354- (bool)testKeyEventsAreSentToFramework:(
id)engineMock {
355 id binaryMessengerMock = OCMProtocolMock(
@protocol(FlutterBinaryMessenger));
357 [engineMock binaryMessenger])
358 .andReturn(binaryMessengerMock);
361 OCMStub([engineMock keyboardManager]).andReturn(keyboardManager);
367 @selector(respondFalseForSendEvent:
callback:userData:));
371 NSDictionary* expectedEvent = @{
372 @"keymap" :
@"macos",
373 @"type" :
@"keydown",
375 @"modifiers" : @(538968064),
376 @"characters" :
@".",
377 @"charactersIgnoringModifiers" :
@".",
380 CGEventRef cgEvent = CGEventCreateKeyboardEvent(NULL, 65,
TRUE);
381 NSEvent*
event = [NSEvent eventWithCGEvent:cgEvent];
382 [viewController viewWillAppear];
383 [viewController keyDown:event];
386 [binaryMessengerMock sendOnChannel:
@"flutter/keyevent"
388 binaryReply:[OCMArg any]]);
396- (bool)testCtrlTabKeyEventIsPropagated:(
id)engineMock {
397 __block
bool called =
false;
402 .andDo((^(NSInvocation* invocation) {
404 [invocation getArgument:&event atIndex:2];
410 OCMStub([engineMock keyboardManager]).andReturn(keyboardManager);
416 NSEvent*
event = [NSEvent keyEventWithType:NSEventTypeKeyDown
418 modifierFlags:0x40101
423 charactersIgnoringModifiers:@""
426 const uint64_t kPhysicalKeyTab = 0x7002b;
428 [viewController viewWillAppear];
430 NSWindow*
window = [[NSWindow alloc] initWithContentRect:NSMakeRect(0, 0, 800, 600)
431 styleMask:NSBorderlessWindowMask
432 backing:NSBackingStoreBuffered
435 [window makeFirstResponder:viewController.flutterView];
436 [viewController.view performKeyEquivalent:event];
440 EXPECT_EQ(last_event.physical, kPhysicalKeyTab);
444- (bool)testKeyEquivalentIsPassedToTextInputPlugin:(
id)engineMock {
445 __block
bool called =
false;
450 .andDo((^(NSInvocation* invocation) {
452 [invocation getArgument:&event atIndex:2];
460 NSEvent*
event = [NSEvent keyEventWithType:NSEventTypeKeyDown
462 modifierFlags:0x40101
467 charactersIgnoringModifiers:@""
470 const uint64_t kPhysicalKeyTab = 0x7002b;
472 [viewController viewWillAppear];
474 NSWindow*
window = [[NSWindow alloc] initWithContentRect:NSMakeRect(0, 0, 800, 600)
475 styleMask:NSBorderlessWindowMask
476 backing:NSBackingStoreBuffered
480 NSDictionary* setClientConfig = @{
488 [viewController.engine.textInputPlugin handleMethodCall:methodCall result:result];
490 [viewController.engine.textInputPlugin
497 [window makeFirstResponder:viewController.engine.textInputPlugin];
499 [viewController.view performKeyEquivalent:event];
503 EXPECT_EQ(last_event.physical, kPhysicalKeyTab);
507- (bool)testKeyEventsArePropagatedIfNotHandled:(
id)engineMock {
508 id binaryMessengerMock = OCMProtocolMock(
@protocol(FlutterBinaryMessenger));
510 [engineMock binaryMessenger])
511 .andReturn(binaryMessengerMock);
514 OCMStub([engineMock keyboardManager]).andReturn(keyboardManager);
519 @selector(respondFalseForSendEvent:
callback:userData:));
523 id responderMock = flutter::testing::mockResponder();
526 NSDictionary* expectedEvent = @{
527 @"keymap" :
@"macos",
528 @"type" :
@"keydown",
530 @"modifiers" : @(538968064),
531 @"characters" :
@".",
532 @"charactersIgnoringModifiers" :
@".",
535 CGEventRef cgEvent = CGEventCreateKeyboardEvent(NULL, 65,
TRUE);
536 NSEvent*
event = [NSEvent eventWithCGEvent:cgEvent];
538 [binaryMessengerMock sendOnChannel:
@"flutter/keyevent"
540 binaryReply:[OCMArg any]])
541 .andDo((^(NSInvocation* invocation) {
543 [invocation getArgument:&handler atIndex:4];
544 NSDictionary* reply = @{
545 @"handled" : @(
false),
550 [viewController viewWillAppear];
551 [viewController keyDown:event];
554 [responderMock keyDown:[OCMArg any]]);
556 [binaryMessengerMock sendOnChannel:
@"flutter/keyevent"
558 binaryReply:[OCMArg any]]);
565- (bool)testFlutterViewIsConfigured:(
id)engineMock {
567 OCMStub([engineMock renderer]).andReturn(renderer_);
572 [viewController loadView];
576 OCMVerify([engineMock renderer]);
584- (bool)testFlagsChangedEventsArePropagatedIfNotHandled:(
id)engineMock {
585 id binaryMessengerMock = OCMProtocolMock(
@protocol(FlutterBinaryMessenger));
587 [engineMock binaryMessenger])
588 .andReturn(binaryMessengerMock);
591 OCMStub([engineMock keyboardManager]).andReturn(keyboardManager);
596 @selector(respondFalseForSendEvent:
callback:userData:));
600 id responderMock = flutter::testing::mockResponder();
603 NSDictionary* expectedEvent = @{
604 @"keymap" :
@"macos",
605 @"type" :
@"keydown",
607 @"modifiers" : @(537001986),
610 CGEventRef cgEvent = CGEventCreateKeyboardEvent(NULL, 56,
TRUE);
611 CGEventSetType(cgEvent, kCGEventFlagsChanged);
612 NSEvent*
event = [NSEvent eventWithCGEvent:cgEvent];
614 [binaryMessengerMock sendOnChannel:
@"flutter/keyevent"
616 binaryReply:[OCMArg any]])
617 .andDo((^(NSInvocation* invocation) {
619 [invocation getArgument:&handler atIndex:4];
620 NSDictionary* reply = @{
621 @"handled" : @(
false),
626 [viewController viewWillAppear];
627 [viewController flagsChanged:event];
630 [binaryMessengerMock sendOnChannel:
@"flutter/keyevent"
632 binaryReply:[OCMArg any]]);
633 }
@catch (NSException* e) {
634 NSLog(
@"%@", e.reason);
640- (bool)testKeyEventsAreNotPropagatedIfHandled:(
id)engineMock {
641 id binaryMessengerMock = OCMProtocolMock(
@protocol(FlutterBinaryMessenger));
643 [engineMock binaryMessenger])
644 .andReturn(binaryMessengerMock);
647 OCMStub([engineMock keyboardManager]).andReturn(keyboardManager);
652 @selector(respondFalseForSendEvent:
callback:userData:));
656 id responderMock = flutter::testing::mockResponder();
659 NSDictionary* expectedEvent = @{
660 @"keymap" :
@"macos",
661 @"type" :
@"keydown",
663 @"modifiers" : @(538968064),
664 @"characters" :
@".",
665 @"charactersIgnoringModifiers" :
@".",
668 CGEventRef cgEvent = CGEventCreateKeyboardEvent(NULL, 65,
TRUE);
669 NSEvent*
event = [NSEvent eventWithCGEvent:cgEvent];
671 [binaryMessengerMock sendOnChannel:
@"flutter/keyevent"
673 binaryReply:[OCMArg any]])
674 .andDo((^(NSInvocation* invocation) {
676 [invocation getArgument:&handler atIndex:4];
677 NSDictionary* reply = @{
678 @"handled" : @(
true),
683 [viewController viewWillAppear];
684 [viewController keyDown:event];
687 never(), [responderMock keyDown:[OCMArg any]]);
689 [binaryMessengerMock sendOnChannel:
@"flutter/keyevent"
691 binaryReply:[OCMArg any]]);
698- (bool)testKeyboardIsRestartedOnEngineRestart:(
id)engineMock {
699 id binaryMessengerMock = OCMProtocolMock(
@protocol(FlutterBinaryMessenger));
701 [engineMock binaryMessenger])
702 .andReturn(binaryMessengerMock);
703 __block
bool called =
false;
708 OCMStub([engineMock keyboardManager]).andDo(^(NSInvocation* invocation) {
709 [invocation setReturnValue:&keyboardManager];
715 .andDo((^(NSInvocation* invocation) {
717 [invocation getArgument:&event atIndex:2];
725 [viewController viewWillAppear];
726 NSEvent* keyADown = [NSEvent keyEventWithType:NSEventTypeKeyDown
733 charactersIgnoringModifiers:@"a"
743 [viewController keyDown:keyADown];
746 EXPECT_EQ(last_event.physical, kPhysicalKeyA);
751 [viewController keyDown:keyADown];
754 EXPECT_EQ(last_event.physical, kPhysicalKeyA);
760 userData:(nullable
void*)userData {
766- (bool)testTrackpadGesturesAreSentToFramework:(
id)engineMock {
769 OCMStub([engineMock renderer]).andReturn(renderer_);
770 __block
bool called =
false;
773 .andDo((^(NSInvocation* invocation) {
775 [invocation getArgument:&event atIndex:2];
783 [viewController loadView];
787 CGEventRef cgEventStart = CGEventCreateScrollWheelEvent(NULL, kCGScrollEventUnitPixel, 1, 0);
788 CGEventSetType(cgEventStart, kCGEventScrollWheel);
789 CGEventSetIntegerValueField(cgEventStart, kCGScrollWheelEventScrollPhase, kCGScrollPhaseBegan);
790 CGEventSetIntegerValueField(cgEventStart, kCGScrollWheelEventIsContinuous, 1);
793 [viewController scrollWheel:[NSEvent eventWithCGEvent:cgEventStart]];
801 CGEventRef cgEventUpdate = CGEventCreateCopy(cgEventStart);
802 CGEventSetIntegerValueField(cgEventUpdate, kCGScrollWheelEventScrollPhase, kCGScrollPhaseChanged);
803 CGEventSetIntegerValueField(cgEventUpdate, kCGScrollWheelEventDeltaAxis2, 1);
804 CGEventSetIntegerValueField(cgEventUpdate, kCGScrollWheelEventDeltaAxis1, 2);
807 [viewController scrollWheel:[NSEvent eventWithCGEvent:cgEventUpdate]];
813 EXPECT_EQ(last_event.pan_x, 8 *
viewController.flutterView.layer.contentsScale);
814 EXPECT_EQ(last_event.pan_y, 16 *
viewController.flutterView.layer.contentsScale);
818 [viewController scrollWheel:[NSEvent eventWithCGEvent:cgEventUpdate]];
824 EXPECT_EQ(last_event.pan_x, 16 *
viewController.flutterView.layer.contentsScale);
825 EXPECT_EQ(last_event.pan_y, 32 *
viewController.flutterView.layer.contentsScale);
828 CGEventRef cgEventEnd = CGEventCreateCopy(cgEventStart);
829 CGEventSetIntegerValueField(cgEventEnd, kCGScrollWheelEventScrollPhase, kCGScrollPhaseEnded);
832 [viewController scrollWheel:[NSEvent eventWithCGEvent:cgEventEnd]];
840 CGEventRef cgEventMomentumStart = CGEventCreateCopy(cgEventStart);
841 CGEventSetIntegerValueField(cgEventMomentumStart, kCGScrollWheelEventScrollPhase, 0);
842 CGEventSetIntegerValueField(cgEventMomentumStart, kCGScrollWheelEventMomentumPhase,
843 kCGMomentumScrollPhaseBegin);
846 [viewController scrollWheel:[NSEvent eventWithCGEvent:cgEventMomentumStart]];
847 EXPECT_FALSE(called);
850 CGEventRef cgEventMomentumUpdate = CGEventCreateCopy(cgEventStart);
851 CGEventSetIntegerValueField(cgEventMomentumUpdate, kCGScrollWheelEventScrollPhase, 0);
852 CGEventSetIntegerValueField(cgEventMomentumUpdate, kCGScrollWheelEventMomentumPhase,
853 kCGMomentumScrollPhaseContinue);
856 [viewController scrollWheel:[NSEvent eventWithCGEvent:cgEventMomentumUpdate]];
857 EXPECT_FALSE(called);
860 id touchMock = OCMClassMock([NSTouch
class]);
861 NSSet* touchSet = [NSSet setWithObject:touchMock];
862 id touchEventMock1 = OCMClassMock([NSEvent
class]);
863 OCMStub([touchEventMock1 allTouches]).andReturn(touchSet);
864 CGPoint touchLocation = {0, 0};
865 OCMStub([touchEventMock1 locationInWindow]).andReturn(touchLocation);
866 OCMStub([(NSEvent*)touchEventMock1 timestamp]).andReturn(0.150);
870 [viewController touchesBeganWithEvent:touchEventMock1];
871 EXPECT_FALSE(called);
874 id touchEventMock2 = OCMClassMock([NSEvent
class]);
875 OCMStub([touchEventMock2 allTouches]).andReturn(touchSet);
876 OCMStub([touchEventMock2 locationInWindow]).andReturn(touchLocation);
877 OCMStub([(NSEvent*)touchEventMock2 timestamp]).andReturn(0.005);
881 [viewController touchesBeganWithEvent:touchEventMock2];
887 CGEventRef cgEventMomentumEnd = CGEventCreateCopy(cgEventStart);
888 CGEventSetIntegerValueField(cgEventMomentumEnd, kCGScrollWheelEventScrollPhase, 0);
889 CGEventSetIntegerValueField(cgEventMomentumEnd, kCGScrollWheelEventMomentumPhase,
890 kCGMomentumScrollPhaseEnd);
893 [viewController scrollWheel:[NSEvent eventWithCGEvent:cgEventMomentumEnd]];
894 EXPECT_FALSE(called);
897 CGEventRef cgEventMayBegin = CGEventCreateCopy(cgEventStart);
898 CGEventSetIntegerValueField(cgEventMayBegin, kCGScrollWheelEventScrollPhase,
899 kCGScrollPhaseMayBegin);
902 [viewController scrollWheel:[NSEvent eventWithCGEvent:cgEventMayBegin]];
910 CGEventRef cgEventCancel = CGEventCreateCopy(cgEventStart);
911 CGEventSetIntegerValueField(cgEventCancel, kCGScrollWheelEventScrollPhase,
912 kCGScrollPhaseCancelled);
915 [viewController scrollWheel:[NSEvent eventWithCGEvent:cgEventCancel]];
923 CGEventRef cgEventDiscrete = CGEventCreateScrollWheelEvent(NULL, kCGScrollEventUnitPixel, 1, 0);
924 CGEventSetType(cgEventDiscrete, kCGEventScrollWheel);
925 CGEventSetIntegerValueField(cgEventDiscrete, kCGScrollWheelEventIsContinuous, 0);
926 CGEventSetIntegerValueField(cgEventDiscrete, kCGScrollWheelEventDeltaAxis2, 1);
927 CGEventSetIntegerValueField(cgEventDiscrete, kCGScrollWheelEventDeltaAxis1, 2);
930 [viewController scrollWheel:[NSEvent eventWithCGEvent:cgEventDiscrete]];
934 EXPECT_EQ(last_event.scroll_delta_x, -40 *
viewController.flutterView.layer.contentsScale);
935 EXPECT_EQ(last_event.scroll_delta_y, -80 *
viewController.flutterView.layer.contentsScale);
939 CGEventRef cgEventDiscreteShift =
940 CGEventCreateScrollWheelEvent(NULL, kCGScrollEventUnitPixel, 1, 0);
941 CGEventSetType(cgEventDiscreteShift, kCGEventScrollWheel);
942 CGEventSetFlags(cgEventDiscreteShift,
static_cast<uint64_t
>(kCGEventFlagMaskShift) |
944 CGEventSetIntegerValueField(cgEventDiscreteShift, kCGScrollWheelEventIsContinuous, 0);
945 CGEventSetIntegerValueField(cgEventDiscreteShift, kCGScrollWheelEventDeltaAxis2,
947 CGEventSetIntegerValueField(cgEventDiscreteShift, kCGScrollWheelEventDeltaAxis1,
951 [viewController scrollWheel:[NSEvent eventWithCGEvent:cgEventDiscreteShift]];
955 EXPECT_FLOAT_EQ(last_event.scroll_delta_x, 0.0 *
viewController.flutterView.layer.contentsScale);
956 EXPECT_FLOAT_EQ(last_event.scroll_delta_y,
962 [viewController magnifyWithEvent:flutter::testing::MockGestureEvent(NSEventTypeMagnify,
963 NSEventPhaseBegan, 1, 0)];
972 [viewController magnifyWithEvent:flutter::testing::MockGestureEvent(NSEventTypeMagnify,
973 NSEventPhaseChanged, 1, 0)];
979 EXPECT_EQ(last_event.pan_x, 0);
980 EXPECT_EQ(last_event.pan_y, 0);
981 EXPECT_EQ(last_event.scale, 2);
983 EXPECT_EQ(last_event.rotation, 0);
987 [viewController magnifyWithEvent:flutter::testing::MockGestureEvent(NSEventTypeMagnify,
988 NSEventPhaseChanged, 1, 0)];
994 EXPECT_EQ(last_event.pan_x, 0);
995 EXPECT_EQ(last_event.pan_y, 0);
996 EXPECT_EQ(last_event.scale, 4);
998 EXPECT_EQ(last_event.rotation, 0);
1002 [viewController magnifyWithEvent:flutter::testing::MockGestureEvent(NSEventTypeMagnify,
1003 NSEventPhaseEnded, 0, 0)];
1004 EXPECT_TRUE(called);
1013 [viewController rotateWithEvent:flutter::testing::MockGestureEvent(NSEventTypeRotate,
1014 NSEventPhaseBegan, 1, 0)];
1015 EXPECT_TRUE(called);
1023 [viewController rotateWithEvent:flutter::testing::MockGestureEvent(
1024 NSEventTypeRotate, NSEventPhaseChanged, 0, -180)];
1025 EXPECT_TRUE(called);
1030 EXPECT_EQ(last_event.pan_x, 0);
1031 EXPECT_EQ(last_event.pan_y, 0);
1032 EXPECT_EQ(last_event.scale, 1);
1033 EXPECT_EQ(last_event.rotation, M_PI);
1037 [viewController rotateWithEvent:flutter::testing::MockGestureEvent(
1038 NSEventTypeRotate, NSEventPhaseChanged, 0, -360)];
1039 EXPECT_TRUE(called);
1044 EXPECT_EQ(last_event.pan_x, 0);
1045 EXPECT_EQ(last_event.pan_y, 0);
1046 EXPECT_EQ(last_event.scale, 1);
1047 EXPECT_EQ(last_event.rotation, 3 * M_PI);
1051 [viewController rotateWithEvent:flutter::testing::MockGestureEvent(NSEventTypeRotate,
1052 NSEventPhaseEnded, 0, 0)];
1053 EXPECT_TRUE(called);
1061 [viewController rotateWithEvent:flutter::testing::MockGestureEvent(NSEventTypeRotate,
1062 NSEventPhaseCancelled, 0, 0)];
1063 EXPECT_FALSE(called);
1069- (bool)mouseAndGestureEventsAreHandledSeparately:(
id)engineMock {
1073 [viewController loadView];
1078 CGEventCreateScrollWheelEvent(NULL, kCGScrollEventUnitPixel, 1, 0));
1079 CGEventSetType(cgEventStart, kCGEventScrollWheel);
1080 CGEventSetIntegerValueField(cgEventStart, kCGScrollWheelEventScrollPhase, kCGScrollPhaseBegan);
1081 CGEventSetIntegerValueField(cgEventStart, kCGScrollWheelEventIsContinuous, 1);
1082 [viewController scrollWheel:[NSEvent eventWithCGEvent:cgEventStart]];
1085 CGEventSetIntegerValueField(cgEventUpdate, kCGScrollWheelEventScrollPhase, kCGScrollPhaseChanged);
1086 CGEventSetIntegerValueField(cgEventUpdate, kCGScrollWheelEventDeltaAxis2, 1);
1087 CGEventSetIntegerValueField(cgEventUpdate, kCGScrollWheelEventDeltaAxis1, 2);
1088 [viewController scrollWheel:[NSEvent eventWithCGEvent:cgEventUpdate]];
1090 NSEvent* mouseEvent = flutter::testing::CreateMouseEvent(0x00);
1091 [viewController mouseEntered:mouseEvent];
1092 [viewController mouseExited:mouseEvent];
1096 CGEventSetIntegerValueField(cgEventEnd, kCGScrollWheelEventScrollPhase, kCGScrollPhaseEnded);
1097 [viewController scrollWheel:[NSEvent eventWithCGEvent:cgEventEnd]];