9 #import <OCMock/OCMock.h>
11 #include "flutter/fml/platform/darwin/cf_utils.h"
19 #include "flutter/shell/platform/embedder/test_utils/key_codes.g.h"
20 #include "flutter/testing/autoreleasepool_test.h"
21 #include "flutter/testing/testing.h"
23 #pragma mark - Test Helper Classes
30 @property(nonatomic) FlutterKeyEvent*
data;
31 - (nonnull instancetype)initWithEvent:(
const FlutterKeyEvent*)event;
35 - (instancetype)initWithEvent:(const FlutterKeyEvent*)event {
37 _data =
new FlutterKeyEvent(*event);
57 - (instancetype)initWithResponder:(NSResponder*)responder {
58 if (
self = [super init]) {
64 - (void)keyDown:(NSEvent*)event {
65 [_responder keyDown:event];
68 - (void)keyUp:(NSEvent*)event {
69 [_responder keyUp:event];
72 - (BOOL)performKeyEquivalent:(NSEvent*)event {
73 return [_responder performKeyEquivalent:event];
76 - (void)flagsChanged:(NSEvent*)event {
77 [_responder flagsChanged:event];
85 @property(nonatomic, assign) BOOL mouseDownCalled;
86 @property(nonatomic, assign) BOOL mouseUpCalled;
90 - (void)mouseDown:(NSEvent*)event {
94 - (void)mouseUp:(NSEvent*)event {
95 self.mouseUpCalled = YES;
100 - (bool)testKeyEventsAreSentToFramework:(
id)mockEngine;
101 - (bool)testKeyEventsArePropagatedIfNotHandled:(
id)mockEngine;
102 - (bool)testKeyEventsAreNotPropagatedIfHandled:(
id)mockEngine;
103 - (bool)testCtrlTabKeyEventIsPropagated:(
id)mockEngine;
104 - (bool)testKeyEquivalentIsPassedToTextInputPlugin:(
id)mockEngine;
105 - (bool)testFlagsChangedEventsArePropagatedIfNotHandled:(
id)mockEngine;
106 - (bool)testKeyboardIsRestartedOnEngineRestart:(
id)mockEngine;
107 - (bool)testTrackpadGesturesAreSentToFramework:(
id)mockEngine;
108 - (bool)mouseAndGestureEventsAreHandledSeparately:(
id)engineMock;
109 - (bool)testMouseDownUpEventsSentToNextResponder:(
id)mockEngine;
110 - (bool)testModifierKeysAreSynthesizedOnMouseMove:(
id)mockEngine;
111 - (bool)testViewWillAppearCalledMultipleTimes:(
id)mockEngine;
112 - (bool)testFlutterViewIsConfigured:(
id)mockEngine;
117 + (void)respondFalseForSendEvent:(const FlutterKeyEvent&)event
118 callback:(nullable FlutterKeyEventCallback)callback
119 userData:(nullable
void*)userData;
122 #pragma mark - Static helper functions
124 using namespace ::flutter::testing::keycodes;
130 id MockGestureEvent(NSEventType type, NSEventPhase phase,
double magnification,
double rotation) {
131 id event = [OCMockObject mockForClass:[NSEvent class]];
132 NSPoint locationInWindow = NSMakePoint(0, 0);
135 NSTimeInterval timestamp = 1;
136 NSUInteger modifierFlags = 0;
137 [(NSEvent*)[[event stub] andReturnValue:OCMOCK_VALUE(type)] type];
138 [(NSEvent*)[[event stub] andReturnValue:OCMOCK_VALUE(phase)] phase];
139 [(NSEvent*)[[event stub] andReturnValue:OCMOCK_VALUE(locationInWindow)] locationInWindow];
140 [(NSEvent*)[[event stub] andReturnValue:OCMOCK_VALUE(deltaX)] deltaX];
141 [(NSEvent*)[[event stub] andReturnValue:OCMOCK_VALUE(deltaY)] deltaY];
142 [(NSEvent*)[[event stub] andReturnValue:OCMOCK_VALUE(timestamp)] timestamp];
143 [(NSEvent*)[[event stub] andReturnValue:OCMOCK_VALUE(modifierFlags)] modifierFlags];
144 [(NSEvent*)[[event stub] andReturnValue:OCMOCK_VALUE(magnification)] magnification];
145 [(NSEvent*)[[event stub] andReturnValue:OCMOCK_VALUE(rotation)] rotation];
151 NSString* fixtures = @(testing::GetFixturesPath());
153 initWithAssetsPath:fixtures
154 ICUDataPath:[fixtures stringByAppendingString:@"/icudtl.dat"]];
155 return [[
FlutterEngine alloc] initWithName:@"test" project:project allowHeadlessExecution:true];
158 NSResponder* mockResponder() {
159 NSResponder* mock = OCMStrictClassMock([NSResponder
class]);
160 OCMStub([mock keyDown:[OCMArg any]]).andDo(nil);
161 OCMStub([mock keyUp:[OCMArg any]]).andDo(nil);
162 OCMStub([mock flagsChanged:[OCMArg any]]).andDo(nil);
166 NSEvent* CreateMouseEvent(NSEventModifierFlags modifierFlags) {
167 return [NSEvent mouseEventWithType:NSEventTypeMouseMoved
169 modifierFlags:modifierFlags
180 #pragma mark - gtest tests
189 [viewControllerMock loadView];
190 auto subViews = [viewControllerMock.view subviews];
192 EXPECT_EQ([subViews count], 1u);
193 EXPECT_EQ(subViews[0], viewControllerMock.flutterView);
195 NSTextField* textField = [[NSTextField alloc] initWithFrame:NSMakeRect(0, 0, 1, 1)];
196 [viewControllerMock.view addSubview:textField];
198 subViews = [viewControllerMock.view subviews];
199 EXPECT_EQ([subViews count], 2u);
201 auto accessibilityChildren = viewControllerMock.view.accessibilityChildren;
203 EXPECT_EQ([accessibilityChildren count], 1u);
204 EXPECT_EQ(accessibilityChildren[0], viewControllerMock.flutterView);
209 [viewControllerMock loadView];
210 EXPECT_EQ([viewControllerMock.flutterView acceptsFirstMouse:nil], YES);
218 [viewController loadView];
220 NSWindow* window = [[NSWindow alloc] initWithContentRect:NSMakeRect(0, 0, 800, 600)
221 styleMask:NSBorderlessWindowMask
222 backing:NSBackingStoreBuffered
224 window.contentView = viewController.view;
225 NSView* dummyView = [[NSView alloc] initWithFrame:CGRectZero];
226 [viewController.view addSubview:dummyView];
228 [dummyView addSubview:viewController.textInputPlugin];
230 EXPECT_TRUE([window makeFirstResponder:viewController.textInputPlugin]);
231 EXPECT_EQ([window firstResponder], viewController.textInputPlugin);
232 EXPECT_FALSE(viewController.textInputPlugin.superview == viewController.view);
235 EXPECT_TRUE(viewController.textInputPlugin.superview == viewController.view);
239 NSString* fixtures = @(testing::GetFixturesPath());
241 initWithAssetsPath:fixtures
242 ICUDataPath:[fixtures stringByAppendingString:@"/icudtl.dat"]];
245 ASSERT_EQ(viewController.
mouseTrackingMode, kFlutterMouseTrackingModeInActiveApp);
249 id mockEngine = GetMockEngine();
254 id mockEngine = GetMockEngine();
260 id mockEngine = GetMockEngine();
266 id mockEngine = GetMockEngine();
271 id mockEngine = GetMockEngine();
273 testKeyEquivalentIsPassedToTextInputPlugin:mockEngine]);
277 id mockEngine = GetMockEngine();
279 testFlagsChangedEventsArePropagatedIfNotHandled:mockEngine]);
283 id mockEngine = GetMockEngine();
289 id mockEngine = GetMockEngine();
295 id mockEngine = GetMockEngine();
301 id mockEngine = GetMockEngine();
307 id mockEngine = GetMockEngine();
313 id mockEngine = GetMockEngine();
319 id mockEngine = GetMockEngine();
337 #pragma mark - FlutterViewControllerTestObjC
341 - (bool)testKeyEventsAreSentToFramework:(
id)engineMock {
344 [engineMock binaryMessenger])
345 .andReturn(binaryMessengerMock);
350 @selector(respondFalseForSendEvent:callback:userData:));
354 NSDictionary* expectedEvent = @{
355 @"keymap" :
@"macos",
356 @"type" :
@"keydown",
358 @"modifiers" : @(538968064),
359 @"characters" :
@".",
360 @"charactersIgnoringModifiers" :
@".",
363 CGEventRef cgEvent = CGEventCreateKeyboardEvent(NULL, 65, TRUE);
364 NSEvent*
event = [NSEvent eventWithCGEvent:cgEvent];
365 [viewController viewWillAppear];
366 [viewController keyDown:event];
369 [binaryMessengerMock sendOnChannel:
@"flutter/keyevent"
370 message:encodedKeyEvent
371 binaryReply:[OCMArg any]]);
379 - (bool)testCtrlTabKeyEventIsPropagated:(
id)engineMock {
380 __block
bool called =
false;
381 __block FlutterKeyEvent last_event;
385 .andDo((^(NSInvocation* invocation) {
386 FlutterKeyEvent* event;
387 [invocation getArgument:&event atIndex:2];
395 NSEvent*
event = [NSEvent keyEventWithType:NSEventTypeKeyDown
397 modifierFlags:0x40101
402 charactersIgnoringModifiers:@""
405 const uint64_t kPhysicalKeyTab = 0x7002b;
407 [viewController viewWillAppear];
409 NSWindow* window = [[NSWindow alloc] initWithContentRect:NSMakeRect(0, 0, 800, 600)
410 styleMask:NSBorderlessWindowMask
411 backing:NSBackingStoreBuffered
413 window.contentView = viewController.view;
414 [window makeFirstResponder:viewController.flutterView];
415 [viewController.view performKeyEquivalent:event];
418 EXPECT_EQ(last_event.type, kFlutterKeyEventTypeDown);
419 EXPECT_EQ(last_event.physical, kPhysicalKeyTab);
423 - (bool)testKeyEquivalentIsPassedToTextInputPlugin:(
id)engineMock {
424 __block
bool called =
false;
425 __block FlutterKeyEvent last_event;
429 .andDo((^(NSInvocation* invocation) {
430 FlutterKeyEvent* event;
431 [invocation getArgument:&event atIndex:2];
439 NSEvent*
event = [NSEvent keyEventWithType:NSEventTypeKeyDown
441 modifierFlags:0x40101
446 charactersIgnoringModifiers:@""
449 const uint64_t kPhysicalKeyTab = 0x7002b;
451 [viewController viewWillAppear];
453 NSWindow* window = [[NSWindow alloc] initWithContentRect:NSMakeRect(0, 0, 800, 600)
454 styleMask:NSBorderlessWindowMask
455 backing:NSBackingStoreBuffered
457 window.contentView = viewController.view;
459 [viewController.view addSubview:viewController.textInputPlugin];
463 [window makeFirstResponder:viewController.textInputPlugin];
465 [viewController.view performKeyEquivalent:event];
468 EXPECT_EQ(last_event.type, kFlutterKeyEventTypeDown);
469 EXPECT_EQ(last_event.physical, kPhysicalKeyTab);
473 - (bool)testKeyEventsArePropagatedIfNotHandled:(
id)engineMock {
476 [engineMock binaryMessenger])
477 .andReturn(binaryMessengerMock);
482 @selector(respondFalseForSendEvent:callback:userData:));
486 id responderMock = flutter::testing::mockResponder();
488 viewController.nextResponder = responderWrapper;
489 NSDictionary* expectedEvent = @{
490 @"keymap" :
@"macos",
491 @"type" :
@"keydown",
493 @"modifiers" : @(538968064),
494 @"characters" :
@".",
495 @"charactersIgnoringModifiers" :
@".",
498 CGEventRef cgEvent = CGEventCreateKeyboardEvent(NULL, 65, TRUE);
499 NSEvent*
event = [NSEvent eventWithCGEvent:cgEvent];
501 [binaryMessengerMock sendOnChannel:
@"flutter/keyevent"
502 message:encodedKeyEvent
503 binaryReply:[OCMArg any]])
504 .andDo((^(NSInvocation* invocation) {
506 [invocation getArgument:&handler atIndex:4];
507 NSDictionary* reply = @{
508 @"handled" : @(
false),
511 handler(encodedReply);
513 [viewController viewWillAppear];
514 [viewController keyDown:event];
517 [responderMock keyDown:[OCMArg any]]);
519 [binaryMessengerMock sendOnChannel:
@"flutter/keyevent"
520 message:encodedKeyEvent
521 binaryReply:[OCMArg any]]);
528 - (bool)testFlutterViewIsConfigured:(
id)engineMock {
530 OCMStub([engineMock renderer]).andReturn(renderer_);
535 [viewController loadView];
539 OCMVerify([engineMock renderer]);
547 - (bool)testFlagsChangedEventsArePropagatedIfNotHandled:(
id)engineMock {
550 [engineMock binaryMessenger])
551 .andReturn(binaryMessengerMock);
556 @selector(respondFalseForSendEvent:callback:userData:));
560 id responderMock = flutter::testing::mockResponder();
562 viewController.nextResponder = responderWrapper;
563 NSDictionary* expectedEvent = @{
564 @"keymap" :
@"macos",
565 @"type" :
@"keydown",
567 @"modifiers" : @(537001986),
570 CGEventRef cgEvent = CGEventCreateKeyboardEvent(NULL, 56, TRUE);
571 CGEventSetType(cgEvent, kCGEventFlagsChanged);
572 NSEvent*
event = [NSEvent eventWithCGEvent:cgEvent];
574 [binaryMessengerMock sendOnChannel:
@"flutter/keyevent"
575 message:encodedKeyEvent
576 binaryReply:[OCMArg any]])
577 .andDo((^(NSInvocation* invocation) {
579 [invocation getArgument:&handler atIndex:4];
580 NSDictionary* reply = @{
581 @"handled" : @(
false),
584 handler(encodedReply);
586 [viewController viewWillAppear];
587 [viewController flagsChanged:event];
590 [binaryMessengerMock sendOnChannel:
@"flutter/keyevent"
591 message:encodedKeyEvent
592 binaryReply:[OCMArg any]]);
593 }
@catch (NSException* e) {
594 NSLog(
@"%@", e.reason);
600 - (bool)testKeyEventsAreNotPropagatedIfHandled:(
id)engineMock {
603 [engineMock binaryMessenger])
604 .andReturn(binaryMessengerMock);
609 @selector(respondFalseForSendEvent:callback:userData:));
613 id responderMock = flutter::testing::mockResponder();
615 viewController.nextResponder = responderWrapper;
616 NSDictionary* expectedEvent = @{
617 @"keymap" :
@"macos",
618 @"type" :
@"keydown",
620 @"modifiers" : @(538968064),
621 @"characters" :
@".",
622 @"charactersIgnoringModifiers" :
@".",
625 CGEventRef cgEvent = CGEventCreateKeyboardEvent(NULL, 65, TRUE);
626 NSEvent*
event = [NSEvent eventWithCGEvent:cgEvent];
628 [binaryMessengerMock sendOnChannel:
@"flutter/keyevent"
629 message:encodedKeyEvent
630 binaryReply:[OCMArg any]])
631 .andDo((^(NSInvocation* invocation) {
633 [invocation getArgument:&handler atIndex:4];
634 NSDictionary* reply = @{
635 @"handled" : @(
true),
638 handler(encodedReply);
640 [viewController viewWillAppear];
641 [viewController keyDown:event];
644 never(), [responderMock keyDown:[OCMArg any]]);
646 [binaryMessengerMock sendOnChannel:
@"flutter/keyevent"
647 message:encodedKeyEvent
648 binaryReply:[OCMArg any]]);
655 - (bool)testKeyboardIsRestartedOnEngineRestart:(
id)engineMock {
658 [engineMock binaryMessenger])
659 .andReturn(binaryMessengerMock);
660 __block
bool called =
false;
661 __block FlutterKeyEvent last_event;
665 .andDo((^(NSInvocation* invocation) {
666 FlutterKeyEvent* event;
667 [invocation getArgument:&event atIndex:2];
675 [viewController viewWillAppear];
676 NSEvent* keyADown = [NSEvent keyEventWithType:NSEventTypeKeyDown
683 charactersIgnoringModifiers:@"a"
686 const uint64_t kPhysicalKeyA = 0x70004;
693 [viewController keyDown:keyADown];
695 EXPECT_EQ(last_event.type, kFlutterKeyEventTypeDown);
696 EXPECT_EQ(last_event.physical, kPhysicalKeyA);
701 [viewController keyDown:keyADown];
703 EXPECT_EQ(last_event.type, kFlutterKeyEventTypeDown);
704 EXPECT_EQ(last_event.physical, kPhysicalKeyA);
708 + (void)respondFalseForSendEvent:(const FlutterKeyEvent&)event
709 callback:(nullable FlutterKeyEventCallback)callback
710 userData:(nullable
void*)userData {
711 if (callback !=
nullptr) {
712 callback(
false, userData);
716 - (bool)testTrackpadGesturesAreSentToFramework:(
id)engineMock {
719 OCMStub([engineMock renderer]).andReturn(renderer_);
720 __block
bool called =
false;
721 __block FlutterPointerEvent last_event;
723 .andDo((^(NSInvocation* invocation) {
724 FlutterPointerEvent* event;
725 [invocation getArgument:&event atIndex:2];
733 [viewController loadView];
737 CGEventRef cgEventStart = CGEventCreateScrollWheelEvent(NULL, kCGScrollEventUnitPixel, 1, 0);
738 CGEventSetType(cgEventStart, kCGEventScrollWheel);
739 CGEventSetIntegerValueField(cgEventStart, kCGScrollWheelEventScrollPhase, kCGScrollPhaseBegan);
740 CGEventSetIntegerValueField(cgEventStart, kCGScrollWheelEventIsContinuous, 1);
743 [viewController scrollWheel:[NSEvent eventWithCGEvent:cgEventStart]];
745 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
746 EXPECT_EQ(last_event.phase, kPanZoomStart);
747 EXPECT_EQ(last_event.device_kind, kFlutterPointerDeviceKindTrackpad);
748 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
751 CGEventRef cgEventUpdate = CGEventCreateCopy(cgEventStart);
752 CGEventSetIntegerValueField(cgEventUpdate, kCGScrollWheelEventScrollPhase, kCGScrollPhaseChanged);
753 CGEventSetIntegerValueField(cgEventUpdate, kCGScrollWheelEventDeltaAxis2, 1);
754 CGEventSetIntegerValueField(cgEventUpdate, kCGScrollWheelEventDeltaAxis1, 2);
757 [viewController scrollWheel:[NSEvent eventWithCGEvent:cgEventUpdate]];
759 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
760 EXPECT_EQ(last_event.phase, kPanZoomUpdate);
761 EXPECT_EQ(last_event.device_kind, kFlutterPointerDeviceKindTrackpad);
762 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
763 EXPECT_EQ(last_event.pan_x, 8 * viewController.flutterView.layer.contentsScale);
764 EXPECT_EQ(last_event.pan_y, 16 * viewController.flutterView.layer.contentsScale);
768 [viewController scrollWheel:[NSEvent eventWithCGEvent:cgEventUpdate]];
770 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
771 EXPECT_EQ(last_event.phase, kPanZoomUpdate);
772 EXPECT_EQ(last_event.device_kind, kFlutterPointerDeviceKindTrackpad);
773 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
774 EXPECT_EQ(last_event.pan_x, 16 * viewController.flutterView.layer.contentsScale);
775 EXPECT_EQ(last_event.pan_y, 32 * viewController.flutterView.layer.contentsScale);
778 CGEventRef cgEventEnd = CGEventCreateCopy(cgEventStart);
779 CGEventSetIntegerValueField(cgEventEnd, kCGScrollWheelEventScrollPhase, kCGScrollPhaseEnded);
782 [viewController scrollWheel:[NSEvent eventWithCGEvent:cgEventEnd]];
784 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
785 EXPECT_EQ(last_event.phase, kPanZoomEnd);
786 EXPECT_EQ(last_event.device_kind, kFlutterPointerDeviceKindTrackpad);
787 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
790 CGEventRef cgEventMomentumStart = CGEventCreateCopy(cgEventStart);
791 CGEventSetIntegerValueField(cgEventMomentumStart, kCGScrollWheelEventScrollPhase, 0);
792 CGEventSetIntegerValueField(cgEventMomentumStart, kCGScrollWheelEventMomentumPhase,
793 kCGMomentumScrollPhaseBegin);
796 [viewController scrollWheel:[NSEvent eventWithCGEvent:cgEventMomentumStart]];
797 EXPECT_FALSE(called);
800 CGEventRef cgEventMomentumUpdate = CGEventCreateCopy(cgEventStart);
801 CGEventSetIntegerValueField(cgEventMomentumUpdate, kCGScrollWheelEventScrollPhase, 0);
802 CGEventSetIntegerValueField(cgEventMomentumUpdate, kCGScrollWheelEventMomentumPhase,
803 kCGMomentumScrollPhaseContinue);
806 [viewController scrollWheel:[NSEvent eventWithCGEvent:cgEventMomentumUpdate]];
807 EXPECT_FALSE(called);
810 id touchMock = OCMClassMock([NSTouch
class]);
811 NSSet* touchSet = [NSSet setWithObject:touchMock];
812 id touchEventMock1 = OCMClassMock([NSEvent
class]);
813 OCMStub([touchEventMock1 allTouches]).andReturn(touchSet);
814 CGPoint touchLocation = {0, 0};
815 OCMStub([touchEventMock1 locationInWindow]).andReturn(touchLocation);
816 OCMStub([(NSEvent*)touchEventMock1 timestamp]).andReturn(0.150);
820 [viewController touchesBeganWithEvent:touchEventMock1];
821 EXPECT_FALSE(called);
824 id touchEventMock2 = OCMClassMock([NSEvent
class]);
825 OCMStub([touchEventMock2 allTouches]).andReturn(touchSet);
826 OCMStub([touchEventMock2 locationInWindow]).andReturn(touchLocation);
827 OCMStub([(NSEvent*)touchEventMock2 timestamp]).andReturn(0.005);
831 [viewController touchesBeganWithEvent:touchEventMock2];
833 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindScrollInertiaCancel);
834 EXPECT_EQ(last_event.device_kind, kFlutterPointerDeviceKindTrackpad);
837 CGEventRef cgEventMomentumEnd = CGEventCreateCopy(cgEventStart);
838 CGEventSetIntegerValueField(cgEventMomentumEnd, kCGScrollWheelEventScrollPhase, 0);
839 CGEventSetIntegerValueField(cgEventMomentumEnd, kCGScrollWheelEventMomentumPhase,
840 kCGMomentumScrollPhaseEnd);
843 [viewController scrollWheel:[NSEvent eventWithCGEvent:cgEventMomentumEnd]];
844 EXPECT_FALSE(called);
847 CGEventRef cgEventMayBegin = CGEventCreateCopy(cgEventStart);
848 CGEventSetIntegerValueField(cgEventMayBegin, kCGScrollWheelEventScrollPhase,
849 kCGScrollPhaseMayBegin);
852 [viewController scrollWheel:[NSEvent eventWithCGEvent:cgEventMayBegin]];
854 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
855 EXPECT_EQ(last_event.phase, kPanZoomStart);
856 EXPECT_EQ(last_event.device_kind, kFlutterPointerDeviceKindTrackpad);
857 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
860 CGEventRef cgEventCancel = CGEventCreateCopy(cgEventStart);
861 CGEventSetIntegerValueField(cgEventCancel, kCGScrollWheelEventScrollPhase,
862 kCGScrollPhaseCancelled);
865 [viewController scrollWheel:[NSEvent eventWithCGEvent:cgEventCancel]];
867 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
868 EXPECT_EQ(last_event.phase, kPanZoomEnd);
869 EXPECT_EQ(last_event.device_kind, kFlutterPointerDeviceKindTrackpad);
870 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
873 CGEventRef cgEventDiscrete = CGEventCreateScrollWheelEvent(NULL, kCGScrollEventUnitPixel, 1, 0);
874 CGEventSetType(cgEventDiscrete, kCGEventScrollWheel);
875 CGEventSetIntegerValueField(cgEventDiscrete, kCGScrollWheelEventIsContinuous, 0);
876 CGEventSetIntegerValueField(cgEventDiscrete, kCGScrollWheelEventDeltaAxis2, 1);
877 CGEventSetIntegerValueField(cgEventDiscrete, kCGScrollWheelEventDeltaAxis1, 2);
880 [viewController scrollWheel:[NSEvent eventWithCGEvent:cgEventDiscrete]];
882 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindScroll);
884 EXPECT_EQ(last_event.scroll_delta_x, -40 * viewController.flutterView.layer.contentsScale);
885 EXPECT_EQ(last_event.scroll_delta_y, -80 * viewController.flutterView.layer.contentsScale);
889 CGEventRef cgEventDiscreteShift =
890 CGEventCreateScrollWheelEvent(NULL, kCGScrollEventUnitPixel, 1, 0);
891 CGEventSetType(cgEventDiscreteShift, kCGEventScrollWheel);
893 CGEventSetIntegerValueField(cgEventDiscreteShift, kCGScrollWheelEventIsContinuous, 0);
894 CGEventSetIntegerValueField(cgEventDiscreteShift, kCGScrollWheelEventDeltaAxis2,
896 CGEventSetIntegerValueField(cgEventDiscreteShift, kCGScrollWheelEventDeltaAxis1,
900 [viewController scrollWheel:[NSEvent eventWithCGEvent:cgEventDiscreteShift]];
902 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindScroll);
904 EXPECT_FLOAT_EQ(last_event.scroll_delta_x, 0.0 * viewController.flutterView.layer.contentsScale);
905 EXPECT_FLOAT_EQ(last_event.scroll_delta_y,
906 -80.0 * viewController.flutterView.layer.contentsScale);
911 [viewController magnifyWithEvent:flutter::testing::MockGestureEvent(NSEventTypeMagnify,
912 NSEventPhaseBegan, 1, 0)];
914 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
915 EXPECT_EQ(last_event.phase, kPanZoomStart);
916 EXPECT_EQ(last_event.device_kind, kFlutterPointerDeviceKindTrackpad);
917 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
921 [viewController magnifyWithEvent:flutter::testing::MockGestureEvent(NSEventTypeMagnify,
922 NSEventPhaseChanged, 1, 0)];
924 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
925 EXPECT_EQ(last_event.phase, kPanZoomUpdate);
926 EXPECT_EQ(last_event.device_kind, kFlutterPointerDeviceKindTrackpad);
927 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
928 EXPECT_EQ(last_event.pan_x, 0);
929 EXPECT_EQ(last_event.pan_y, 0);
930 EXPECT_EQ(last_event.scale, 2);
932 EXPECT_EQ(last_event.rotation, 0);
936 [viewController magnifyWithEvent:flutter::testing::MockGestureEvent(NSEventTypeMagnify,
937 NSEventPhaseChanged, 1, 0)];
939 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
940 EXPECT_EQ(last_event.phase, kPanZoomUpdate);
941 EXPECT_EQ(last_event.device_kind, kFlutterPointerDeviceKindTrackpad);
942 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
943 EXPECT_EQ(last_event.pan_x, 0);
944 EXPECT_EQ(last_event.pan_y, 0);
945 EXPECT_EQ(last_event.scale, 4);
947 EXPECT_EQ(last_event.rotation, 0);
951 [viewController magnifyWithEvent:flutter::testing::MockGestureEvent(NSEventTypeMagnify,
952 NSEventPhaseEnded, 0, 0)];
954 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
955 EXPECT_EQ(last_event.phase, kPanZoomEnd);
956 EXPECT_EQ(last_event.device_kind, kFlutterPointerDeviceKindTrackpad);
957 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
962 [viewController rotateWithEvent:flutter::testing::MockGestureEvent(NSEventTypeRotate,
963 NSEventPhaseBegan, 1, 0)];
965 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
966 EXPECT_EQ(last_event.phase, kPanZoomStart);
967 EXPECT_EQ(last_event.device_kind, kFlutterPointerDeviceKindTrackpad);
968 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
972 [viewController rotateWithEvent:flutter::testing::MockGestureEvent(
973 NSEventTypeRotate, NSEventPhaseChanged, 0, -180)];
975 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
976 EXPECT_EQ(last_event.phase, kPanZoomUpdate);
977 EXPECT_EQ(last_event.device_kind, kFlutterPointerDeviceKindTrackpad);
978 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
979 EXPECT_EQ(last_event.pan_x, 0);
980 EXPECT_EQ(last_event.pan_y, 0);
981 EXPECT_EQ(last_event.scale, 1);
982 EXPECT_EQ(last_event.rotation, M_PI);
986 [viewController rotateWithEvent:flutter::testing::MockGestureEvent(
987 NSEventTypeRotate, NSEventPhaseChanged, 0, -360)];
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, 1);
996 EXPECT_EQ(last_event.rotation, 3 * M_PI);
1000 [viewController rotateWithEvent:flutter::testing::MockGestureEvent(NSEventTypeRotate,
1001 NSEventPhaseEnded, 0, 0)];
1002 EXPECT_TRUE(called);
1003 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
1004 EXPECT_EQ(last_event.phase, kPanZoomEnd);
1005 EXPECT_EQ(last_event.device_kind, kFlutterPointerDeviceKindTrackpad);
1006 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
1010 [viewController rotateWithEvent:flutter::testing::MockGestureEvent(NSEventTypeRotate,
1011 NSEventPhaseCancelled, 0, 0)];
1012 EXPECT_FALSE(called);
1018 - (bool)mouseAndGestureEventsAreHandledSeparately:(
id)engineMock {
1022 [viewController loadView];
1026 fml::CFRef<CGEventRef> cgEventStart(
1027 CGEventCreateScrollWheelEvent(NULL, kCGScrollEventUnitPixel, 1, 0));
1028 CGEventSetType(cgEventStart, kCGEventScrollWheel);
1029 CGEventSetIntegerValueField(cgEventStart, kCGScrollWheelEventScrollPhase, kCGScrollPhaseBegan);
1030 CGEventSetIntegerValueField(cgEventStart, kCGScrollWheelEventIsContinuous, 1);
1031 [viewController scrollWheel:[NSEvent eventWithCGEvent:cgEventStart]];
1033 fml::CFRef<CGEventRef> cgEventUpdate(CGEventCreateCopy(cgEventStart));
1034 CGEventSetIntegerValueField(cgEventUpdate, kCGScrollWheelEventScrollPhase, kCGScrollPhaseChanged);
1035 CGEventSetIntegerValueField(cgEventUpdate, kCGScrollWheelEventDeltaAxis2, 1);
1036 CGEventSetIntegerValueField(cgEventUpdate, kCGScrollWheelEventDeltaAxis1, 2);
1037 [viewController scrollWheel:[NSEvent eventWithCGEvent:cgEventUpdate]];
1039 NSEvent* mouseEvent = flutter::testing::CreateMouseEvent(0x00);
1040 [viewController mouseEntered:mouseEvent];
1041 [viewController mouseExited:mouseEvent];
1044 fml::CFRef<CGEventRef> cgEventEnd(CGEventCreateCopy(cgEventStart));
1045 CGEventSetIntegerValueField(cgEventEnd, kCGScrollWheelEventScrollPhase, kCGScrollPhaseEnded);
1046 [viewController scrollWheel:[NSEvent eventWithCGEvent:cgEventEnd]];
1051 - (bool)testViewWillAppearCalledMultipleTimes:(
id)engineMock {
1055 [viewController viewWillAppear];
1056 [viewController viewWillAppear];
1064 [key isEqualToString:
@"Contents/Frameworks/App.framework/Resources/flutter_assets/test.png"]);
1072 EXPECT_TRUE([packageKey
1074 @"Contents/Frameworks/App.framework/Resources/flutter_assets/packages/test/test.png"]);
1078 static void SwizzledNoop(
id self,
SEL _cmd) {}
1090 - (bool)testMouseDownUpEventsSentToNextResponder:(
id)engineMock {
1091 if (@available(macOS 13.3.1, *)) {
1101 Method mouseDown = class_getInstanceMethod([NSResponder
class],
@selector(mouseDown:));
1102 Method mouseUp = class_getInstanceMethod([NSResponder
class],
@selector(mouseUp:));
1103 IMP noopImp = (IMP)SwizzledNoop;
1104 IMP origMouseDown = method_setImplementation(mouseDown, noopImp);
1105 IMP origMouseUp = method_setImplementation(mouseUp, noopImp);
1115 NSEvent* mouseEvent = flutter::testing::CreateMouseEvent(0x00);
1116 [view mouseDown:mouseEvent];
1121 [view mouseUp:mouseEvent];
1126 method_setImplementation(mouseDown, origMouseDown);
1127 method_setImplementation(mouseUp, origMouseUp);
1132 - (bool)testModifierKeysAreSynthesizedOnMouseMove:(
id)engineMock {
1135 [engineMock binaryMessenger])
1136 .andReturn(binaryMessengerMock);
1140 OCMStub([engineMock renderer]).andReturn(renderer_);
1143 __block NSMutableArray<KeyEventWrapper*>* events = [NSMutableArray array];
1147 .andDo((^(NSInvocation* invocation) {
1148 FlutterKeyEvent* event;
1149 [invocation getArgument:&event atIndex:2];
1153 __block NSMutableArray<NSDictionary*>* channelEvents = [NSMutableArray array];
1154 OCMStub([binaryMessengerMock sendOnChannel:
@"flutter/keyevent"
1155 message:[OCMArg any]
1156 binaryReply:[OCMArg any]])
1157 .andDo((^(NSInvocation* invocation) {
1159 [invocation getArgument:&data atIndex:3];
1161 [channelEvents addObject:event];
1167 [viewController loadView];
1168 [viewController viewWillAppear];
1171 NSEvent* mouseEvent = flutter::testing::CreateMouseEvent(0x00);
1172 [viewController mouseMoved:mouseEvent];
1173 EXPECT_EQ([events count], 0u);
1177 FlutterKeyEvent* event;
1178 NSDictionary* channelEvent;
1179 NSNumber* logicalKey;
1180 NSNumber* physicalKey;
1181 NSEventModifierFlags flag = [flutter::keyCodeToModifierFlag[keyCode] unsignedLongValue];
1185 flag |= NSEventModifierFlagShift;
1188 flag |= NSEventModifierFlagControl;
1191 flag |= NSEventModifierFlagOption;
1194 flag |= NSEventModifierFlagCommand;
1198 NSEvent* mouseEvent = flutter::testing::CreateMouseEvent(flag);
1199 [viewController mouseMoved:mouseEvent];
1200 EXPECT_EQ([events count], 1u);
1201 event = events[0].data;
1202 logicalKey = [flutter::keyCodeToLogicalKey objectForKey:keyCode];
1203 physicalKey = [flutter::keyCodeToPhysicalKey objectForKey:keyCode];
1204 EXPECT_EQ(event->type, kFlutterKeyEventTypeDown);
1205 EXPECT_EQ(event->logical, logicalKey.unsignedLongLongValue);
1206 EXPECT_EQ(event->physical, physicalKey.unsignedLongLongValue);
1207 EXPECT_EQ(event->synthesized,
true);
1209 channelEvent = channelEvents[0];
1210 EXPECT_TRUE([channelEvent[
@"type"] isEqual:
@"keydown"]);
1211 EXPECT_TRUE([channelEvent[
@"keyCode"] isEqual:keyCode]);
1212 EXPECT_TRUE([channelEvent[
@"modifiers"] isEqual:@(flag)]);
1215 mouseEvent = flutter::testing::CreateMouseEvent(0x00);
1216 [viewController mouseMoved:mouseEvent];
1217 EXPECT_EQ([events count], 2u);
1218 event = events[1].data;
1219 logicalKey = [flutter::keyCodeToLogicalKey objectForKey:keyCode];
1220 physicalKey = [flutter::keyCodeToPhysicalKey objectForKey:keyCode];
1221 EXPECT_EQ(event->type, kFlutterKeyEventTypeUp);
1222 EXPECT_EQ(event->logical, logicalKey.unsignedLongLongValue);
1223 EXPECT_EQ(event->physical, physicalKey.unsignedLongLongValue);
1224 EXPECT_EQ(event->synthesized,
true);
1226 channelEvent = channelEvents[1];
1227 EXPECT_TRUE([channelEvent[
@"type"] isEqual:
@"keyup"]);
1228 EXPECT_TRUE([channelEvent[
@"keyCode"] isEqual:keyCode]);
1229 EXPECT_TRUE([channelEvent[
@"modifiers"] isEqual:@(0)]);
1231 [events removeAllObjects];
1232 [channelEvents removeAllObjects];
1244 OCMStub([engineMock renderer]).andReturn(renderer_);
1249 [viewController loadView];
1250 weakController = viewController;
1252 [engineMock shutDownEngine];
1255 EXPECT_EQ(weakController, nil);