9 #import <OCMock/OCMock.h>
11 #include "flutter/common/constants.h"
12 #include "flutter/fml/platform/darwin/cf_utils.h"
20 #include "flutter/shell/platform/embedder/test_utils/key_codes.g.h"
21 #include "flutter/testing/autoreleasepool_test.h"
22 #include "flutter/testing/testing.h"
24 #pragma mark - Test Helper Classes
31 @property(nonatomic) FlutterKeyEvent*
data;
32 - (nonnull instancetype)initWithEvent:(
const FlutterKeyEvent*)event;
36 - (instancetype)initWithEvent:(
const FlutterKeyEvent*)
event {
38 _data =
new FlutterKeyEvent(*event);
58 - (instancetype)initWithResponder:(NSResponder*)responder {
59 if (
self = [super init]) {
65 - (void)keyDown:(NSEvent*)event {
66 [_responder keyDown:event];
69 - (void)keyUp:(NSEvent*)event {
70 [_responder keyUp:event];
73 - (BOOL)performKeyEquivalent:(NSEvent*)event {
74 return [_responder performKeyEquivalent:event];
77 - (void)flagsChanged:(NSEvent*)event {
78 [_responder flagsChanged:event];
86 @property(nonatomic, assign) BOOL mouseDownCalled;
87 @property(nonatomic, assign) BOOL mouseUpCalled;
91 - (void)mouseDown:(NSEvent*)event {
95 - (void)mouseUp:(NSEvent*)event {
96 self.mouseUpCalled = YES;
101 - (bool)testKeyEventsAreSentToFramework:(
id)mockEngine;
102 - (bool)testKeyEventsArePropagatedIfNotHandled:(
id)mockEngine;
103 - (bool)testKeyEventsAreNotPropagatedIfHandled:(
id)mockEngine;
104 - (bool)testCtrlTabKeyEventIsPropagated:(
id)mockEngine;
105 - (bool)testKeyEquivalentIsPassedToTextInputPlugin:(
id)mockEngine;
106 - (bool)testFlagsChangedEventsArePropagatedIfNotHandled:(
id)mockEngine;
107 - (bool)testKeyboardIsRestartedOnEngineRestart:(
id)mockEngine;
108 - (bool)testTrackpadGesturesAreSentToFramework:(
id)mockEngine;
109 - (bool)mouseAndGestureEventsAreHandledSeparately:(
id)engineMock;
110 - (bool)testMouseDownUpEventsSentToNextResponder:(
id)mockEngine;
111 - (bool)testModifierKeysAreSynthesizedOnMouseMove:(
id)mockEngine;
112 - (bool)testViewWillAppearCalledMultipleTimes:(
id)mockEngine;
113 - (bool)testFlutterViewIsConfigured:(
id)mockEngine;
118 + (void)respondFalseForSendEvent:(const FlutterKeyEvent&)event
119 callback:(nullable FlutterKeyEventCallback)callback
120 userData:(nullable
void*)userData;
123 #pragma mark - Static helper functions
125 using namespace ::flutter::testing::keycodes;
131 id MockGestureEvent(NSEventType type, NSEventPhase phase,
double magnification,
double rotation) {
132 id event = [OCMockObject mockForClass:[NSEvent class]];
133 NSPoint locationInWindow = NSMakePoint(0, 0);
136 NSTimeInterval timestamp = 1;
137 NSUInteger modifierFlags = 0;
138 [(NSEvent*)[[event stub] andReturnValue:OCMOCK_VALUE(type)] type];
139 [(NSEvent*)[[event stub] andReturnValue:OCMOCK_VALUE(phase)] phase];
140 [(NSEvent*)[[event stub] andReturnValue:OCMOCK_VALUE(locationInWindow)] locationInWindow];
141 [(NSEvent*)[[event stub] andReturnValue:OCMOCK_VALUE(deltaX)] deltaX];
142 [(NSEvent*)[[event stub] andReturnValue:OCMOCK_VALUE(deltaY)] deltaY];
143 [(NSEvent*)[[event stub] andReturnValue:OCMOCK_VALUE(timestamp)] timestamp];
144 [(NSEvent*)[[event stub] andReturnValue:OCMOCK_VALUE(modifierFlags)] modifierFlags];
145 [(NSEvent*)[[event stub] andReturnValue:OCMOCK_VALUE(magnification)] magnification];
146 [(NSEvent*)[[event stub] andReturnValue:OCMOCK_VALUE(rotation)] rotation];
152 NSString* fixtures = @(testing::GetFixturesPath());
154 initWithAssetsPath:fixtures
155 ICUDataPath:[fixtures stringByAppendingString:@"/icudtl.dat"]];
156 return [[
FlutterEngine alloc] initWithName:@"test" project:project allowHeadlessExecution:true];
159 NSResponder* mockResponder() {
160 NSResponder* mock = OCMStrictClassMock([NSResponder
class]);
161 OCMStub([mock keyDown:[OCMArg any]]).andDo(nil);
162 OCMStub([mock keyUp:[OCMArg any]]).andDo(nil);
163 OCMStub([mock flagsChanged:[OCMArg any]]).andDo(nil);
167 NSEvent* 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
225 window.contentView = viewController.view;
226 NSView* dummyView = [[NSView alloc] initWithFrame:CGRectZero];
227 [viewController.view addSubview:dummyView];
229 NSDictionary* setClientConfig = @{
230 @"viewId" : @(flutter::kFlutterImplicitViewId),
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);
245 EXPECT_FALSE(engine.textInputPlugin.superview == viewController.view);
248 EXPECT_TRUE(viewController.
engine.textInputPlugin.superview == viewController.view);
252 NSString* fixtures = @(testing::GetFixturesPath());
254 initWithAssetsPath:fixtures
255 ICUDataPath:[fixtures stringByAppendingString:@"/icudtl.dat"]];
258 ASSERT_EQ(viewController.
mouseTrackingMode, kFlutterMouseTrackingModeInActiveApp);
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 {
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"
387 message:encodedKeyEvent
388 binaryReply:[OCMArg any]]);
396 - (bool)testCtrlTabKeyEventIsPropagated:(
id)engineMock {
397 __block
bool called =
false;
398 __block FlutterKeyEvent last_event;
402 .andDo((^(NSInvocation* invocation) {
403 FlutterKeyEvent* event;
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
434 window.contentView = viewController.view;
435 [window makeFirstResponder:viewController.flutterView];
436 [viewController.view performKeyEquivalent:event];
439 EXPECT_EQ(last_event.type, kFlutterKeyEventTypeDown);
440 EXPECT_EQ(last_event.physical, kPhysicalKeyTab);
444 - (bool)testKeyEquivalentIsPassedToTextInputPlugin:(
id)engineMock {
445 __block
bool called =
false;
446 __block FlutterKeyEvent last_event;
450 .andDo((^(NSInvocation* invocation) {
451 FlutterKeyEvent* event;
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
478 window.contentView = viewController.view;
480 NSDictionary* setClientConfig = @{
481 @"viewId" : @(flutter::kFlutterImplicitViewId),
488 [viewController.engine.textInputPlugin handleMethodCall:methodCall result:result];
490 [viewController.engine.textInputPlugin
497 [window makeFirstResponder:viewController.engine.textInputPlugin];
499 [viewController.view performKeyEquivalent:event];
502 EXPECT_EQ(last_event.type, kFlutterKeyEventTypeDown);
503 EXPECT_EQ(last_event.physical, kPhysicalKeyTab);
507 - (bool)testKeyEventsArePropagatedIfNotHandled:(
id)engineMock {
510 [engineMock binaryMessenger])
511 .andReturn(binaryMessengerMock);
514 OCMStub([engineMock keyboardManager]).andReturn(keyboardManager);
519 @selector(respondFalseForSendEvent:callback:userData:));
523 id responderMock = flutter::testing::mockResponder();
525 viewController.nextResponder = responderWrapper;
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"
539 message:encodedKeyEvent
540 binaryReply:[OCMArg any]])
541 .andDo((^(NSInvocation* invocation) {
543 [invocation getArgument:&handler atIndex:4];
544 NSDictionary* reply = @{
545 @"handled" : @(
false),
548 handler(encodedReply);
550 [viewController viewWillAppear];
551 [viewController keyDown:event];
554 [responderMock keyDown:[OCMArg any]]);
556 [binaryMessengerMock sendOnChannel:
@"flutter/keyevent"
557 message:encodedKeyEvent
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 {
587 [engineMock binaryMessenger])
588 .andReturn(binaryMessengerMock);
591 OCMStub([engineMock keyboardManager]).andReturn(keyboardManager);
596 @selector(respondFalseForSendEvent:callback:userData:));
600 id responderMock = flutter::testing::mockResponder();
602 viewController.nextResponder = responderWrapper;
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"
615 message:encodedKeyEvent
616 binaryReply:[OCMArg any]])
617 .andDo((^(NSInvocation* invocation) {
619 [invocation getArgument:&handler atIndex:4];
620 NSDictionary* reply = @{
621 @"handled" : @(
false),
624 handler(encodedReply);
626 [viewController viewWillAppear];
627 [viewController flagsChanged:event];
630 [binaryMessengerMock sendOnChannel:
@"flutter/keyevent"
631 message:encodedKeyEvent
632 binaryReply:[OCMArg any]]);
633 }
@catch (NSException* e) {
634 NSLog(
@"%@", e.reason);
640 - (bool)testKeyEventsAreNotPropagatedIfHandled:(
id)engineMock {
643 [engineMock binaryMessenger])
644 .andReturn(binaryMessengerMock);
647 OCMStub([engineMock keyboardManager]).andReturn(keyboardManager);
652 @selector(respondFalseForSendEvent:callback:userData:));
656 id responderMock = flutter::testing::mockResponder();
658 viewController.nextResponder = responderWrapper;
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"
672 message:encodedKeyEvent
673 binaryReply:[OCMArg any]])
674 .andDo((^(NSInvocation* invocation) {
676 [invocation getArgument:&handler atIndex:4];
677 NSDictionary* reply = @{
678 @"handled" : @(
true),
681 handler(encodedReply);
683 [viewController viewWillAppear];
684 [viewController keyDown:event];
687 never(), [responderMock keyDown:[OCMArg any]]);
689 [binaryMessengerMock sendOnChannel:
@"flutter/keyevent"
690 message:encodedKeyEvent
691 binaryReply:[OCMArg any]]);
698 - (bool)testKeyboardIsRestartedOnEngineRestart:(
id)engineMock {
701 [engineMock binaryMessenger])
702 .andReturn(binaryMessengerMock);
703 __block
bool called =
false;
704 __block FlutterKeyEvent last_event;
708 OCMStub([engineMock keyboardManager]).andDo(^(NSInvocation* invocation) {
709 [invocation setReturnValue:&keyboardManager];
715 .andDo((^(NSInvocation* invocation) {
716 FlutterKeyEvent* event;
717 [invocation getArgument:&event atIndex:2];
725 [viewController viewWillAppear];
726 NSEvent* keyADown = [NSEvent keyEventWithType:NSEventTypeKeyDown
733 charactersIgnoringModifiers:@"a"
736 const uint64_t kPhysicalKeyA = 0x70004;
743 [viewController keyDown:keyADown];
745 EXPECT_EQ(last_event.type, kFlutterKeyEventTypeDown);
746 EXPECT_EQ(last_event.physical, kPhysicalKeyA);
751 [viewController keyDown:keyADown];
753 EXPECT_EQ(last_event.type, kFlutterKeyEventTypeDown);
754 EXPECT_EQ(last_event.physical, kPhysicalKeyA);
758 + (void)respondFalseForSendEvent:(const FlutterKeyEvent&)event
759 callback:(nullable FlutterKeyEventCallback)callback
760 userData:(nullable
void*)userData {
761 if (callback !=
nullptr) {
762 callback(
false, userData);
766 - (bool)testTrackpadGesturesAreSentToFramework:(
id)engineMock {
769 OCMStub([engineMock renderer]).andReturn(renderer_);
770 __block
bool called =
false;
771 __block FlutterPointerEvent last_event;
773 .andDo((^(NSInvocation* invocation) {
774 FlutterPointerEvent* event;
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]];
795 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
796 EXPECT_EQ(last_event.phase, kPanZoomStart);
797 EXPECT_EQ(last_event.device_kind, kFlutterPointerDeviceKindTrackpad);
798 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
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]];
809 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
810 EXPECT_EQ(last_event.phase, kPanZoomUpdate);
811 EXPECT_EQ(last_event.device_kind, kFlutterPointerDeviceKindTrackpad);
812 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
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]];
820 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
821 EXPECT_EQ(last_event.phase, kPanZoomUpdate);
822 EXPECT_EQ(last_event.device_kind, kFlutterPointerDeviceKindTrackpad);
823 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
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]];
834 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
835 EXPECT_EQ(last_event.phase, kPanZoomEnd);
836 EXPECT_EQ(last_event.device_kind, kFlutterPointerDeviceKindTrackpad);
837 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
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];
883 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindScrollInertiaCancel);
884 EXPECT_EQ(last_event.device_kind, kFlutterPointerDeviceKindTrackpad);
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]];
904 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
905 EXPECT_EQ(last_event.phase, kPanZoomStart);
906 EXPECT_EQ(last_event.device_kind, kFlutterPointerDeviceKindTrackpad);
907 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
910 CGEventRef cgEventCancel = CGEventCreateCopy(cgEventStart);
911 CGEventSetIntegerValueField(cgEventCancel, kCGScrollWheelEventScrollPhase,
912 kCGScrollPhaseCancelled);
915 [viewController scrollWheel:[NSEvent eventWithCGEvent:cgEventCancel]];
917 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
918 EXPECT_EQ(last_event.phase, kPanZoomEnd);
919 EXPECT_EQ(last_event.device_kind, kFlutterPointerDeviceKindTrackpad);
920 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
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]];
932 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindScroll);
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);
943 CGEventSetIntegerValueField(cgEventDiscreteShift, kCGScrollWheelEventIsContinuous, 0);
944 CGEventSetIntegerValueField(cgEventDiscreteShift, kCGScrollWheelEventDeltaAxis2,
946 CGEventSetIntegerValueField(cgEventDiscreteShift, kCGScrollWheelEventDeltaAxis1,
950 [viewController scrollWheel:[NSEvent eventWithCGEvent:cgEventDiscreteShift]];
952 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindScroll);
954 EXPECT_FLOAT_EQ(last_event.scroll_delta_x, 0.0 * viewController.flutterView.layer.contentsScale);
955 EXPECT_FLOAT_EQ(last_event.scroll_delta_y,
956 -80.0 * viewController.flutterView.layer.contentsScale);
961 [viewController magnifyWithEvent:flutter::testing::MockGestureEvent(NSEventTypeMagnify,
962 NSEventPhaseBegan, 1, 0)];
964 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
965 EXPECT_EQ(last_event.phase, kPanZoomStart);
966 EXPECT_EQ(last_event.device_kind, kFlutterPointerDeviceKindTrackpad);
967 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
971 [viewController magnifyWithEvent:flutter::testing::MockGestureEvent(NSEventTypeMagnify,
972 NSEventPhaseChanged, 1, 0)];
974 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
975 EXPECT_EQ(last_event.phase, kPanZoomUpdate);
976 EXPECT_EQ(last_event.device_kind, kFlutterPointerDeviceKindTrackpad);
977 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
978 EXPECT_EQ(last_event.pan_x, 0);
979 EXPECT_EQ(last_event.pan_y, 0);
980 EXPECT_EQ(last_event.scale, 2);
982 EXPECT_EQ(last_event.rotation, 0);
986 [viewController magnifyWithEvent:flutter::testing::MockGestureEvent(NSEventTypeMagnify,
987 NSEventPhaseChanged, 1, 0)];
989 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
990 EXPECT_EQ(last_event.phase, kPanZoomUpdate);
991 EXPECT_EQ(last_event.device_kind, kFlutterPointerDeviceKindTrackpad);
992 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
993 EXPECT_EQ(last_event.pan_x, 0);
994 EXPECT_EQ(last_event.pan_y, 0);
995 EXPECT_EQ(last_event.scale, 4);
997 EXPECT_EQ(last_event.rotation, 0);
1001 [viewController magnifyWithEvent:flutter::testing::MockGestureEvent(NSEventTypeMagnify,
1002 NSEventPhaseEnded, 0, 0)];
1003 EXPECT_TRUE(called);
1004 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
1005 EXPECT_EQ(last_event.phase, kPanZoomEnd);
1006 EXPECT_EQ(last_event.device_kind, kFlutterPointerDeviceKindTrackpad);
1007 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
1012 [viewController rotateWithEvent:flutter::testing::MockGestureEvent(NSEventTypeRotate,
1013 NSEventPhaseBegan, 1, 0)];
1014 EXPECT_TRUE(called);
1015 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
1016 EXPECT_EQ(last_event.phase, kPanZoomStart);
1017 EXPECT_EQ(last_event.device_kind, kFlutterPointerDeviceKindTrackpad);
1018 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
1022 [viewController rotateWithEvent:flutter::testing::MockGestureEvent(
1023 NSEventTypeRotate, NSEventPhaseChanged, 0, -180)];
1024 EXPECT_TRUE(called);
1025 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
1026 EXPECT_EQ(last_event.phase, kPanZoomUpdate);
1027 EXPECT_EQ(last_event.device_kind, kFlutterPointerDeviceKindTrackpad);
1028 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
1029 EXPECT_EQ(last_event.pan_x, 0);
1030 EXPECT_EQ(last_event.pan_y, 0);
1031 EXPECT_EQ(last_event.scale, 1);
1032 EXPECT_EQ(last_event.rotation, M_PI);
1036 [viewController rotateWithEvent:flutter::testing::MockGestureEvent(
1037 NSEventTypeRotate, NSEventPhaseChanged, 0, -360)];
1038 EXPECT_TRUE(called);
1039 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
1040 EXPECT_EQ(last_event.phase, kPanZoomUpdate);
1041 EXPECT_EQ(last_event.device_kind, kFlutterPointerDeviceKindTrackpad);
1042 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
1043 EXPECT_EQ(last_event.pan_x, 0);
1044 EXPECT_EQ(last_event.pan_y, 0);
1045 EXPECT_EQ(last_event.scale, 1);
1046 EXPECT_EQ(last_event.rotation, 3 * M_PI);
1050 [viewController rotateWithEvent:flutter::testing::MockGestureEvent(NSEventTypeRotate,
1051 NSEventPhaseEnded, 0, 0)];
1052 EXPECT_TRUE(called);
1053 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
1054 EXPECT_EQ(last_event.phase, kPanZoomEnd);
1055 EXPECT_EQ(last_event.device_kind, kFlutterPointerDeviceKindTrackpad);
1056 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
1060 [viewController rotateWithEvent:flutter::testing::MockGestureEvent(NSEventTypeRotate,
1061 NSEventPhaseCancelled, 0, 0)];
1062 EXPECT_FALSE(called);
1068 - (bool)mouseAndGestureEventsAreHandledSeparately:(
id)engineMock {
1072 [viewController loadView];
1076 fml::CFRef<CGEventRef> cgEventStart(
1077 CGEventCreateScrollWheelEvent(NULL, kCGScrollEventUnitPixel, 1, 0));
1078 CGEventSetType(cgEventStart, kCGEventScrollWheel);
1079 CGEventSetIntegerValueField(cgEventStart, kCGScrollWheelEventScrollPhase, kCGScrollPhaseBegan);
1080 CGEventSetIntegerValueField(cgEventStart, kCGScrollWheelEventIsContinuous, 1);
1081 [viewController scrollWheel:[NSEvent eventWithCGEvent:cgEventStart]];
1083 fml::CFRef<CGEventRef> cgEventUpdate(CGEventCreateCopy(cgEventStart));
1084 CGEventSetIntegerValueField(cgEventUpdate, kCGScrollWheelEventScrollPhase, kCGScrollPhaseChanged);
1085 CGEventSetIntegerValueField(cgEventUpdate, kCGScrollWheelEventDeltaAxis2, 1);
1086 CGEventSetIntegerValueField(cgEventUpdate, kCGScrollWheelEventDeltaAxis1, 2);
1087 [viewController scrollWheel:[NSEvent eventWithCGEvent:cgEventUpdate]];
1089 NSEvent* mouseEvent = flutter::testing::CreateMouseEvent(0x00);
1090 [viewController mouseEntered:mouseEvent];
1091 [viewController mouseExited:mouseEvent];
1094 fml::CFRef<CGEventRef> cgEventEnd(CGEventCreateCopy(cgEventStart));
1095 CGEventSetIntegerValueField(cgEventEnd, kCGScrollWheelEventScrollPhase, kCGScrollPhaseEnded);
1096 [viewController scrollWheel:[NSEvent eventWithCGEvent:cgEventEnd]];
1101 - (bool)testViewWillAppearCalledMultipleTimes:(
id)engineMock {
1105 [viewController viewWillAppear];
1106 [viewController viewWillAppear];
1114 [key isEqualToString:
@"Contents/Frameworks/App.framework/Resources/flutter_assets/test.png"]);
1122 EXPECT_TRUE([packageKey
1124 @"Contents/Frameworks/App.framework/Resources/flutter_assets/packages/test/test.png"]);
1128 static void SwizzledNoop(
id self,
SEL _cmd) {}
1140 - (bool)testMouseDownUpEventsSentToNextResponder:(
id)engineMock {
1141 if (@available(macOS 13.3.1, *)) {
1151 Method mouseDown = class_getInstanceMethod([NSResponder
class],
@selector(mouseDown:));
1152 Method mouseUp = class_getInstanceMethod([NSResponder
class],
@selector(mouseUp:));
1153 IMP noopImp = (IMP)SwizzledNoop;
1154 IMP origMouseDown = method_setImplementation(mouseDown, noopImp);
1155 IMP origMouseUp = method_setImplementation(mouseUp, noopImp);
1165 NSEvent* mouseEvent = flutter::testing::CreateMouseEvent(0x00);
1166 [view mouseDown:mouseEvent];
1171 [view mouseUp:mouseEvent];
1176 method_setImplementation(mouseDown, origMouseDown);
1177 method_setImplementation(mouseUp, origMouseUp);
1182 - (bool)testModifierKeysAreSynthesizedOnMouseMove:(
id)engineMock {
1185 [engineMock binaryMessenger])
1186 .andReturn(binaryMessengerMock);
1190 OCMStub([engineMock renderer]).andReturn(renderer_);
1194 OCMStub([engineMock keyboardManager]).andReturn(keyboardManager);
1197 __block NSMutableArray<KeyEventWrapper*>* events = [NSMutableArray array];
1201 .andDo((^(NSInvocation* invocation) {
1202 FlutterKeyEvent* event;
1203 [invocation getArgument:&event atIndex:2];
1207 __block NSMutableArray<NSDictionary*>* channelEvents = [NSMutableArray array];
1208 OCMStub([binaryMessengerMock sendOnChannel:
@"flutter/keyevent"
1209 message:[OCMArg any]
1210 binaryReply:[OCMArg any]])
1211 .andDo((^(NSInvocation* invocation) {
1213 [invocation getArgument:&data atIndex:3];
1215 [channelEvents addObject:event];
1221 [viewController loadView];
1222 [viewController viewWillAppear];
1225 NSEvent* mouseEvent = flutter::testing::CreateMouseEvent(0x00);
1226 [viewController mouseMoved:mouseEvent];
1227 EXPECT_EQ([events count], 0u);
1231 FlutterKeyEvent* event;
1232 NSDictionary* channelEvent;
1233 NSNumber* logicalKey;
1234 NSNumber* physicalKey;
1235 NSEventModifierFlags flag = [flutter::keyCodeToModifierFlag[keyCode] unsignedLongValue];
1239 flag |= NSEventModifierFlagShift;
1242 flag |= NSEventModifierFlagControl;
1245 flag |= NSEventModifierFlagOption;
1248 flag |= NSEventModifierFlagCommand;
1252 NSEvent* mouseEvent = flutter::testing::CreateMouseEvent(flag);
1253 [viewController mouseMoved:mouseEvent];
1254 EXPECT_EQ([events count], 1u);
1255 event = events[0].data;
1256 logicalKey = [flutter::keyCodeToLogicalKey objectForKey:keyCode];
1257 physicalKey = [flutter::keyCodeToPhysicalKey objectForKey:keyCode];
1258 EXPECT_EQ(event->type, kFlutterKeyEventTypeDown);
1259 EXPECT_EQ(event->logical, logicalKey.unsignedLongLongValue);
1260 EXPECT_EQ(event->physical, physicalKey.unsignedLongLongValue);
1261 EXPECT_EQ(event->synthesized,
true);
1263 channelEvent = channelEvents[0];
1264 EXPECT_TRUE([channelEvent[
@"type"] isEqual:
@"keydown"]);
1265 EXPECT_TRUE([channelEvent[
@"keyCode"] isEqual:keyCode]);
1266 EXPECT_TRUE([channelEvent[
@"modifiers"] isEqual:@(flag)]);
1269 mouseEvent = flutter::testing::CreateMouseEvent(0x00);
1270 [viewController mouseMoved:mouseEvent];
1271 EXPECT_EQ([events count], 2u);
1272 event = events[1].data;
1273 logicalKey = [flutter::keyCodeToLogicalKey objectForKey:keyCode];
1274 physicalKey = [flutter::keyCodeToPhysicalKey objectForKey:keyCode];
1275 EXPECT_EQ(event->type, kFlutterKeyEventTypeUp);
1276 EXPECT_EQ(event->logical, logicalKey.unsignedLongLongValue);
1277 EXPECT_EQ(event->physical, physicalKey.unsignedLongLongValue);
1278 EXPECT_EQ(event->synthesized,
true);
1280 channelEvent = channelEvents[1];
1281 EXPECT_TRUE([channelEvent[
@"type"] isEqual:
@"keyup"]);
1282 EXPECT_TRUE([channelEvent[
@"keyCode"] isEqual:keyCode]);
1283 EXPECT_TRUE([channelEvent[
@"modifiers"] isEqual:@(0)]);
1285 [events removeAllObjects];
1286 [channelEvents removeAllObjects];
1298 OCMStub([engineMock renderer]).andReturn(renderer_);
1303 [viewController loadView];
1304 weakController = viewController;
1306 [engineMock shutDownEngine];
1309 EXPECT_EQ(weakController, nil);
NS_ASSUME_NONNULL_BEGIN typedef void(^ FlutterBinaryReply)(NSData *_Nullable reply)
void(^ FlutterResult)(id _Nullable result)
static const FlutterKeyEvent kDefaultFlutterKeyEvent
static const FlutterPointerEvent kDefaultFlutterPointerEvent
instancetype methodCallWithMethodName:arguments:(NSString *method,[arguments] id _Nullable arguments)
nonnull NSString * lookupKeyForAsset:(nonnull NSString *asset)
nonnull NSString * lookupKeyForAsset:fromPackage:(nonnull NSString *asset,[fromPackage] nonnull NSString *package)
FlutterMouseTrackingMode mouseTrackingMode
void onAccessibilityStatusChanged:(BOOL enabled)
bool testLookupKeyAssetsWithPackage()
bool testViewControllerIsReleased()
bool testLookupKeyAssets()
id CreateMockViewController()
id CreateMockFlutterEngine(NSString *pasteboardString)
AutoreleasePoolTest FlutterViewControllerTest
TEST_F(FlutterViewControllerTest, testViewControllerIsReleased)
const NSDictionary * keyCodeToModifierFlag
@ kModifierFlagControlLeft
@ kModifierFlagControlRight
@ kModifierFlagShiftRight
instancetype sharedInstance()