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);
348 OCMStub([engineMock keyboardManager]).andReturn(keyboardManager);
354 @selector(respondFalseForSendEvent:callback:userData:));
358 NSDictionary* expectedEvent = @{
359 @"keymap" :
@"macos",
360 @"type" :
@"keydown",
362 @"modifiers" : @(538968064),
363 @"characters" :
@".",
364 @"charactersIgnoringModifiers" :
@".",
367 CGEventRef cgEvent = CGEventCreateKeyboardEvent(NULL, 65, TRUE);
368 NSEvent*
event = [NSEvent eventWithCGEvent:cgEvent];
369 [viewController viewWillAppear];
370 [viewController keyDown:event];
373 [binaryMessengerMock sendOnChannel:
@"flutter/keyevent"
374 message:encodedKeyEvent
375 binaryReply:[OCMArg any]]);
383 - (bool)testCtrlTabKeyEventIsPropagated:(
id)engineMock {
384 __block
bool called =
false;
385 __block FlutterKeyEvent last_event;
389 .andDo((^(NSInvocation* invocation) {
390 FlutterKeyEvent* event;
391 [invocation getArgument:&event atIndex:2];
397 OCMStub([engineMock keyboardManager]).andReturn(keyboardManager);
403 NSEvent*
event = [NSEvent keyEventWithType:NSEventTypeKeyDown
405 modifierFlags:0x40101
410 charactersIgnoringModifiers:@""
413 const uint64_t kPhysicalKeyTab = 0x7002b;
415 [viewController viewWillAppear];
417 NSWindow* window = [[NSWindow alloc] initWithContentRect:NSMakeRect(0, 0, 800, 600)
418 styleMask:NSBorderlessWindowMask
419 backing:NSBackingStoreBuffered
421 window.contentView = viewController.view;
422 [window makeFirstResponder:viewController.flutterView];
423 [viewController.view performKeyEquivalent:event];
426 EXPECT_EQ(last_event.type, kFlutterKeyEventTypeDown);
427 EXPECT_EQ(last_event.physical, kPhysicalKeyTab);
431 - (bool)testKeyEquivalentIsPassedToTextInputPlugin:(
id)engineMock {
432 __block
bool called =
false;
433 __block FlutterKeyEvent last_event;
437 .andDo((^(NSInvocation* invocation) {
438 FlutterKeyEvent* event;
439 [invocation getArgument:&event atIndex:2];
447 NSEvent*
event = [NSEvent keyEventWithType:NSEventTypeKeyDown
449 modifierFlags:0x40101
454 charactersIgnoringModifiers:@""
457 const uint64_t kPhysicalKeyTab = 0x7002b;
459 [viewController viewWillAppear];
461 NSWindow* window = [[NSWindow alloc] initWithContentRect:NSMakeRect(0, 0, 800, 600)
462 styleMask:NSBorderlessWindowMask
463 backing:NSBackingStoreBuffered
465 window.contentView = viewController.view;
467 [viewController.view addSubview:viewController.textInputPlugin];
471 [window makeFirstResponder:viewController.textInputPlugin];
473 [viewController.view performKeyEquivalent:event];
476 EXPECT_EQ(last_event.type, kFlutterKeyEventTypeDown);
477 EXPECT_EQ(last_event.physical, kPhysicalKeyTab);
481 - (bool)testKeyEventsArePropagatedIfNotHandled:(
id)engineMock {
484 [engineMock binaryMessenger])
485 .andReturn(binaryMessengerMock);
488 OCMStub([engineMock keyboardManager]).andReturn(keyboardManager);
493 @selector(respondFalseForSendEvent:callback:userData:));
497 id responderMock = flutter::testing::mockResponder();
499 viewController.nextResponder = responderWrapper;
500 NSDictionary* expectedEvent = @{
501 @"keymap" :
@"macos",
502 @"type" :
@"keydown",
504 @"modifiers" : @(538968064),
505 @"characters" :
@".",
506 @"charactersIgnoringModifiers" :
@".",
509 CGEventRef cgEvent = CGEventCreateKeyboardEvent(NULL, 65, TRUE);
510 NSEvent*
event = [NSEvent eventWithCGEvent:cgEvent];
512 [binaryMessengerMock sendOnChannel:
@"flutter/keyevent"
513 message:encodedKeyEvent
514 binaryReply:[OCMArg any]])
515 .andDo((^(NSInvocation* invocation) {
517 [invocation getArgument:&handler atIndex:4];
518 NSDictionary* reply = @{
519 @"handled" : @(
false),
522 handler(encodedReply);
524 [viewController viewWillAppear];
525 [viewController keyDown:event];
528 [responderMock keyDown:[OCMArg any]]);
530 [binaryMessengerMock sendOnChannel:
@"flutter/keyevent"
531 message:encodedKeyEvent
532 binaryReply:[OCMArg any]]);
539 - (bool)testFlutterViewIsConfigured:(
id)engineMock {
541 OCMStub([engineMock renderer]).andReturn(renderer_);
546 [viewController loadView];
550 OCMVerify([engineMock renderer]);
558 - (bool)testFlagsChangedEventsArePropagatedIfNotHandled:(
id)engineMock {
561 [engineMock binaryMessenger])
562 .andReturn(binaryMessengerMock);
565 OCMStub([engineMock keyboardManager]).andReturn(keyboardManager);
570 @selector(respondFalseForSendEvent:callback:userData:));
574 id responderMock = flutter::testing::mockResponder();
576 viewController.nextResponder = responderWrapper;
577 NSDictionary* expectedEvent = @{
578 @"keymap" :
@"macos",
579 @"type" :
@"keydown",
581 @"modifiers" : @(537001986),
584 CGEventRef cgEvent = CGEventCreateKeyboardEvent(NULL, 56, TRUE);
585 CGEventSetType(cgEvent, kCGEventFlagsChanged);
586 NSEvent*
event = [NSEvent eventWithCGEvent:cgEvent];
588 [binaryMessengerMock sendOnChannel:
@"flutter/keyevent"
589 message:encodedKeyEvent
590 binaryReply:[OCMArg any]])
591 .andDo((^(NSInvocation* invocation) {
593 [invocation getArgument:&handler atIndex:4];
594 NSDictionary* reply = @{
595 @"handled" : @(
false),
598 handler(encodedReply);
600 [viewController viewWillAppear];
601 [viewController flagsChanged:event];
604 [binaryMessengerMock sendOnChannel:
@"flutter/keyevent"
605 message:encodedKeyEvent
606 binaryReply:[OCMArg any]]);
607 }
@catch (NSException* e) {
608 NSLog(
@"%@", e.reason);
614 - (bool)testKeyEventsAreNotPropagatedIfHandled:(
id)engineMock {
617 [engineMock binaryMessenger])
618 .andReturn(binaryMessengerMock);
621 OCMStub([engineMock keyboardManager]).andReturn(keyboardManager);
626 @selector(respondFalseForSendEvent:callback:userData:));
630 id responderMock = flutter::testing::mockResponder();
632 viewController.nextResponder = responderWrapper;
633 NSDictionary* expectedEvent = @{
634 @"keymap" :
@"macos",
635 @"type" :
@"keydown",
637 @"modifiers" : @(538968064),
638 @"characters" :
@".",
639 @"charactersIgnoringModifiers" :
@".",
642 CGEventRef cgEvent = CGEventCreateKeyboardEvent(NULL, 65, TRUE);
643 NSEvent*
event = [NSEvent eventWithCGEvent:cgEvent];
645 [binaryMessengerMock sendOnChannel:
@"flutter/keyevent"
646 message:encodedKeyEvent
647 binaryReply:[OCMArg any]])
648 .andDo((^(NSInvocation* invocation) {
650 [invocation getArgument:&handler atIndex:4];
651 NSDictionary* reply = @{
652 @"handled" : @(
true),
655 handler(encodedReply);
657 [viewController viewWillAppear];
658 [viewController keyDown:event];
661 never(), [responderMock keyDown:[OCMArg any]]);
663 [binaryMessengerMock sendOnChannel:
@"flutter/keyevent"
664 message:encodedKeyEvent
665 binaryReply:[OCMArg any]]);
672 - (bool)testKeyboardIsRestartedOnEngineRestart:(
id)engineMock {
675 [engineMock binaryMessenger])
676 .andReturn(binaryMessengerMock);
677 __block
bool called =
false;
678 __block FlutterKeyEvent last_event;
682 OCMStub([engineMock keyboardManager]).andDo(^(NSInvocation* invocation) {
683 [invocation setReturnValue:&keyboardManager];
689 .andDo((^(NSInvocation* invocation) {
690 FlutterKeyEvent* event;
691 [invocation getArgument:&event atIndex:2];
699 [viewController viewWillAppear];
700 NSEvent* keyADown = [NSEvent keyEventWithType:NSEventTypeKeyDown
707 charactersIgnoringModifiers:@"a"
710 const uint64_t kPhysicalKeyA = 0x70004;
717 [viewController keyDown:keyADown];
719 EXPECT_EQ(last_event.type, kFlutterKeyEventTypeDown);
720 EXPECT_EQ(last_event.physical, kPhysicalKeyA);
725 [viewController keyDown:keyADown];
727 EXPECT_EQ(last_event.type, kFlutterKeyEventTypeDown);
728 EXPECT_EQ(last_event.physical, kPhysicalKeyA);
732 + (void)respondFalseForSendEvent:(const FlutterKeyEvent&)event
733 callback:(nullable FlutterKeyEventCallback)callback
734 userData:(nullable
void*)userData {
735 if (callback !=
nullptr) {
736 callback(
false, userData);
740 - (bool)testTrackpadGesturesAreSentToFramework:(
id)engineMock {
743 OCMStub([engineMock renderer]).andReturn(renderer_);
744 __block
bool called =
false;
745 __block FlutterPointerEvent last_event;
747 .andDo((^(NSInvocation* invocation) {
748 FlutterPointerEvent* event;
749 [invocation getArgument:&event atIndex:2];
757 [viewController loadView];
761 CGEventRef cgEventStart = CGEventCreateScrollWheelEvent(NULL, kCGScrollEventUnitPixel, 1, 0);
762 CGEventSetType(cgEventStart, kCGEventScrollWheel);
763 CGEventSetIntegerValueField(cgEventStart, kCGScrollWheelEventScrollPhase, kCGScrollPhaseBegan);
764 CGEventSetIntegerValueField(cgEventStart, kCGScrollWheelEventIsContinuous, 1);
767 [viewController scrollWheel:[NSEvent eventWithCGEvent:cgEventStart]];
769 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
770 EXPECT_EQ(last_event.phase, kPanZoomStart);
771 EXPECT_EQ(last_event.device_kind, kFlutterPointerDeviceKindTrackpad);
772 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
775 CGEventRef cgEventUpdate = CGEventCreateCopy(cgEventStart);
776 CGEventSetIntegerValueField(cgEventUpdate, kCGScrollWheelEventScrollPhase, kCGScrollPhaseChanged);
777 CGEventSetIntegerValueField(cgEventUpdate, kCGScrollWheelEventDeltaAxis2, 1);
778 CGEventSetIntegerValueField(cgEventUpdate, kCGScrollWheelEventDeltaAxis1, 2);
781 [viewController scrollWheel:[NSEvent eventWithCGEvent:cgEventUpdate]];
783 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
784 EXPECT_EQ(last_event.phase, kPanZoomUpdate);
785 EXPECT_EQ(last_event.device_kind, kFlutterPointerDeviceKindTrackpad);
786 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
787 EXPECT_EQ(last_event.pan_x, 8 * viewController.flutterView.layer.contentsScale);
788 EXPECT_EQ(last_event.pan_y, 16 * viewController.flutterView.layer.contentsScale);
792 [viewController scrollWheel:[NSEvent eventWithCGEvent:cgEventUpdate]];
794 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
795 EXPECT_EQ(last_event.phase, kPanZoomUpdate);
796 EXPECT_EQ(last_event.device_kind, kFlutterPointerDeviceKindTrackpad);
797 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
798 EXPECT_EQ(last_event.pan_x, 16 * viewController.flutterView.layer.contentsScale);
799 EXPECT_EQ(last_event.pan_y, 32 * viewController.flutterView.layer.contentsScale);
802 CGEventRef cgEventEnd = CGEventCreateCopy(cgEventStart);
803 CGEventSetIntegerValueField(cgEventEnd, kCGScrollWheelEventScrollPhase, kCGScrollPhaseEnded);
806 [viewController scrollWheel:[NSEvent eventWithCGEvent:cgEventEnd]];
808 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
809 EXPECT_EQ(last_event.phase, kPanZoomEnd);
810 EXPECT_EQ(last_event.device_kind, kFlutterPointerDeviceKindTrackpad);
811 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
814 CGEventRef cgEventMomentumStart = CGEventCreateCopy(cgEventStart);
815 CGEventSetIntegerValueField(cgEventMomentumStart, kCGScrollWheelEventScrollPhase, 0);
816 CGEventSetIntegerValueField(cgEventMomentumStart, kCGScrollWheelEventMomentumPhase,
817 kCGMomentumScrollPhaseBegin);
820 [viewController scrollWheel:[NSEvent eventWithCGEvent:cgEventMomentumStart]];
821 EXPECT_FALSE(called);
824 CGEventRef cgEventMomentumUpdate = CGEventCreateCopy(cgEventStart);
825 CGEventSetIntegerValueField(cgEventMomentumUpdate, kCGScrollWheelEventScrollPhase, 0);
826 CGEventSetIntegerValueField(cgEventMomentumUpdate, kCGScrollWheelEventMomentumPhase,
827 kCGMomentumScrollPhaseContinue);
830 [viewController scrollWheel:[NSEvent eventWithCGEvent:cgEventMomentumUpdate]];
831 EXPECT_FALSE(called);
834 id touchMock = OCMClassMock([NSTouch
class]);
835 NSSet* touchSet = [NSSet setWithObject:touchMock];
836 id touchEventMock1 = OCMClassMock([NSEvent
class]);
837 OCMStub([touchEventMock1 allTouches]).andReturn(touchSet);
838 CGPoint touchLocation = {0, 0};
839 OCMStub([touchEventMock1 locationInWindow]).andReturn(touchLocation);
840 OCMStub([(NSEvent*)touchEventMock1 timestamp]).andReturn(0.150);
844 [viewController touchesBeganWithEvent:touchEventMock1];
845 EXPECT_FALSE(called);
848 id touchEventMock2 = OCMClassMock([NSEvent
class]);
849 OCMStub([touchEventMock2 allTouches]).andReturn(touchSet);
850 OCMStub([touchEventMock2 locationInWindow]).andReturn(touchLocation);
851 OCMStub([(NSEvent*)touchEventMock2 timestamp]).andReturn(0.005);
855 [viewController touchesBeganWithEvent:touchEventMock2];
857 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindScrollInertiaCancel);
858 EXPECT_EQ(last_event.device_kind, kFlutterPointerDeviceKindTrackpad);
861 CGEventRef cgEventMomentumEnd = CGEventCreateCopy(cgEventStart);
862 CGEventSetIntegerValueField(cgEventMomentumEnd, kCGScrollWheelEventScrollPhase, 0);
863 CGEventSetIntegerValueField(cgEventMomentumEnd, kCGScrollWheelEventMomentumPhase,
864 kCGMomentumScrollPhaseEnd);
867 [viewController scrollWheel:[NSEvent eventWithCGEvent:cgEventMomentumEnd]];
868 EXPECT_FALSE(called);
871 CGEventRef cgEventMayBegin = CGEventCreateCopy(cgEventStart);
872 CGEventSetIntegerValueField(cgEventMayBegin, kCGScrollWheelEventScrollPhase,
873 kCGScrollPhaseMayBegin);
876 [viewController scrollWheel:[NSEvent eventWithCGEvent:cgEventMayBegin]];
878 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
879 EXPECT_EQ(last_event.phase, kPanZoomStart);
880 EXPECT_EQ(last_event.device_kind, kFlutterPointerDeviceKindTrackpad);
881 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
884 CGEventRef cgEventCancel = CGEventCreateCopy(cgEventStart);
885 CGEventSetIntegerValueField(cgEventCancel, kCGScrollWheelEventScrollPhase,
886 kCGScrollPhaseCancelled);
889 [viewController scrollWheel:[NSEvent eventWithCGEvent:cgEventCancel]];
891 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
892 EXPECT_EQ(last_event.phase, kPanZoomEnd);
893 EXPECT_EQ(last_event.device_kind, kFlutterPointerDeviceKindTrackpad);
894 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
897 CGEventRef cgEventDiscrete = CGEventCreateScrollWheelEvent(NULL, kCGScrollEventUnitPixel, 1, 0);
898 CGEventSetType(cgEventDiscrete, kCGEventScrollWheel);
899 CGEventSetIntegerValueField(cgEventDiscrete, kCGScrollWheelEventIsContinuous, 0);
900 CGEventSetIntegerValueField(cgEventDiscrete, kCGScrollWheelEventDeltaAxis2, 1);
901 CGEventSetIntegerValueField(cgEventDiscrete, kCGScrollWheelEventDeltaAxis1, 2);
904 [viewController scrollWheel:[NSEvent eventWithCGEvent:cgEventDiscrete]];
906 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindScroll);
908 EXPECT_EQ(last_event.scroll_delta_x, -40 * viewController.flutterView.layer.contentsScale);
909 EXPECT_EQ(last_event.scroll_delta_y, -80 * viewController.flutterView.layer.contentsScale);
913 CGEventRef cgEventDiscreteShift =
914 CGEventCreateScrollWheelEvent(NULL, kCGScrollEventUnitPixel, 1, 0);
915 CGEventSetType(cgEventDiscreteShift, kCGEventScrollWheel);
917 CGEventSetIntegerValueField(cgEventDiscreteShift, kCGScrollWheelEventIsContinuous, 0);
918 CGEventSetIntegerValueField(cgEventDiscreteShift, kCGScrollWheelEventDeltaAxis2,
920 CGEventSetIntegerValueField(cgEventDiscreteShift, kCGScrollWheelEventDeltaAxis1,
924 [viewController scrollWheel:[NSEvent eventWithCGEvent:cgEventDiscreteShift]];
926 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindScroll);
928 EXPECT_FLOAT_EQ(last_event.scroll_delta_x, 0.0 * viewController.flutterView.layer.contentsScale);
929 EXPECT_FLOAT_EQ(last_event.scroll_delta_y,
930 -80.0 * viewController.flutterView.layer.contentsScale);
935 [viewController magnifyWithEvent:flutter::testing::MockGestureEvent(NSEventTypeMagnify,
936 NSEventPhaseBegan, 1, 0)];
938 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
939 EXPECT_EQ(last_event.phase, kPanZoomStart);
940 EXPECT_EQ(last_event.device_kind, kFlutterPointerDeviceKindTrackpad);
941 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
945 [viewController magnifyWithEvent:flutter::testing::MockGestureEvent(NSEventTypeMagnify,
946 NSEventPhaseChanged, 1, 0)];
948 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
949 EXPECT_EQ(last_event.phase, kPanZoomUpdate);
950 EXPECT_EQ(last_event.device_kind, kFlutterPointerDeviceKindTrackpad);
951 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
952 EXPECT_EQ(last_event.pan_x, 0);
953 EXPECT_EQ(last_event.pan_y, 0);
954 EXPECT_EQ(last_event.scale, 2);
956 EXPECT_EQ(last_event.rotation, 0);
960 [viewController magnifyWithEvent:flutter::testing::MockGestureEvent(NSEventTypeMagnify,
961 NSEventPhaseChanged, 1, 0)];
963 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
964 EXPECT_EQ(last_event.phase, kPanZoomUpdate);
965 EXPECT_EQ(last_event.device_kind, kFlutterPointerDeviceKindTrackpad);
966 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
967 EXPECT_EQ(last_event.pan_x, 0);
968 EXPECT_EQ(last_event.pan_y, 0);
969 EXPECT_EQ(last_event.scale, 4);
971 EXPECT_EQ(last_event.rotation, 0);
975 [viewController magnifyWithEvent:flutter::testing::MockGestureEvent(NSEventTypeMagnify,
976 NSEventPhaseEnded, 0, 0)];
978 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
979 EXPECT_EQ(last_event.phase, kPanZoomEnd);
980 EXPECT_EQ(last_event.device_kind, kFlutterPointerDeviceKindTrackpad);
981 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
986 [viewController rotateWithEvent:flutter::testing::MockGestureEvent(NSEventTypeRotate,
987 NSEventPhaseBegan, 1, 0)];
989 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
990 EXPECT_EQ(last_event.phase, kPanZoomStart);
991 EXPECT_EQ(last_event.device_kind, kFlutterPointerDeviceKindTrackpad);
992 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
996 [viewController rotateWithEvent:flutter::testing::MockGestureEvent(
997 NSEventTypeRotate, NSEventPhaseChanged, 0, -180)];
999 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
1000 EXPECT_EQ(last_event.phase, kPanZoomUpdate);
1001 EXPECT_EQ(last_event.device_kind, kFlutterPointerDeviceKindTrackpad);
1002 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
1003 EXPECT_EQ(last_event.pan_x, 0);
1004 EXPECT_EQ(last_event.pan_y, 0);
1005 EXPECT_EQ(last_event.scale, 1);
1006 EXPECT_EQ(last_event.rotation, M_PI);
1010 [viewController rotateWithEvent:flutter::testing::MockGestureEvent(
1011 NSEventTypeRotate, NSEventPhaseChanged, 0, -360)];
1012 EXPECT_TRUE(called);
1013 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
1014 EXPECT_EQ(last_event.phase, kPanZoomUpdate);
1015 EXPECT_EQ(last_event.device_kind, kFlutterPointerDeviceKindTrackpad);
1016 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
1017 EXPECT_EQ(last_event.pan_x, 0);
1018 EXPECT_EQ(last_event.pan_y, 0);
1019 EXPECT_EQ(last_event.scale, 1);
1020 EXPECT_EQ(last_event.rotation, 3 * M_PI);
1024 [viewController rotateWithEvent:flutter::testing::MockGestureEvent(NSEventTypeRotate,
1025 NSEventPhaseEnded, 0, 0)];
1026 EXPECT_TRUE(called);
1027 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
1028 EXPECT_EQ(last_event.phase, kPanZoomEnd);
1029 EXPECT_EQ(last_event.device_kind, kFlutterPointerDeviceKindTrackpad);
1030 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
1034 [viewController rotateWithEvent:flutter::testing::MockGestureEvent(NSEventTypeRotate,
1035 NSEventPhaseCancelled, 0, 0)];
1036 EXPECT_FALSE(called);
1042 - (bool)mouseAndGestureEventsAreHandledSeparately:(
id)engineMock {
1046 [viewController loadView];
1050 fml::CFRef<CGEventRef> cgEventStart(
1051 CGEventCreateScrollWheelEvent(NULL, kCGScrollEventUnitPixel, 1, 0));
1052 CGEventSetType(cgEventStart, kCGEventScrollWheel);
1053 CGEventSetIntegerValueField(cgEventStart, kCGScrollWheelEventScrollPhase, kCGScrollPhaseBegan);
1054 CGEventSetIntegerValueField(cgEventStart, kCGScrollWheelEventIsContinuous, 1);
1055 [viewController scrollWheel:[NSEvent eventWithCGEvent:cgEventStart]];
1057 fml::CFRef<CGEventRef> cgEventUpdate(CGEventCreateCopy(cgEventStart));
1058 CGEventSetIntegerValueField(cgEventUpdate, kCGScrollWheelEventScrollPhase, kCGScrollPhaseChanged);
1059 CGEventSetIntegerValueField(cgEventUpdate, kCGScrollWheelEventDeltaAxis2, 1);
1060 CGEventSetIntegerValueField(cgEventUpdate, kCGScrollWheelEventDeltaAxis1, 2);
1061 [viewController scrollWheel:[NSEvent eventWithCGEvent:cgEventUpdate]];
1063 NSEvent* mouseEvent = flutter::testing::CreateMouseEvent(0x00);
1064 [viewController mouseEntered:mouseEvent];
1065 [viewController mouseExited:mouseEvent];
1068 fml::CFRef<CGEventRef> cgEventEnd(CGEventCreateCopy(cgEventStart));
1069 CGEventSetIntegerValueField(cgEventEnd, kCGScrollWheelEventScrollPhase, kCGScrollPhaseEnded);
1070 [viewController scrollWheel:[NSEvent eventWithCGEvent:cgEventEnd]];
1075 - (bool)testViewWillAppearCalledMultipleTimes:(
id)engineMock {
1079 [viewController viewWillAppear];
1080 [viewController viewWillAppear];
1088 [key isEqualToString:
@"Contents/Frameworks/App.framework/Resources/flutter_assets/test.png"]);
1096 EXPECT_TRUE([packageKey
1098 @"Contents/Frameworks/App.framework/Resources/flutter_assets/packages/test/test.png"]);
1102 static void SwizzledNoop(
id self,
SEL _cmd) {}
1114 - (bool)testMouseDownUpEventsSentToNextResponder:(
id)engineMock {
1115 if (@available(macOS 13.3.1, *)) {
1125 Method mouseDown = class_getInstanceMethod([NSResponder
class],
@selector(mouseDown:));
1126 Method mouseUp = class_getInstanceMethod([NSResponder
class],
@selector(mouseUp:));
1127 IMP noopImp = (IMP)SwizzledNoop;
1128 IMP origMouseDown = method_setImplementation(mouseDown, noopImp);
1129 IMP origMouseUp = method_setImplementation(mouseUp, noopImp);
1139 NSEvent* mouseEvent = flutter::testing::CreateMouseEvent(0x00);
1140 [view mouseDown:mouseEvent];
1145 [view mouseUp:mouseEvent];
1150 method_setImplementation(mouseDown, origMouseDown);
1151 method_setImplementation(mouseUp, origMouseUp);
1156 - (bool)testModifierKeysAreSynthesizedOnMouseMove:(
id)engineMock {
1159 [engineMock binaryMessenger])
1160 .andReturn(binaryMessengerMock);
1164 OCMStub([engineMock renderer]).andReturn(renderer_);
1168 OCMStub([engineMock keyboardManager]).andReturn(keyboardManager);
1171 __block NSMutableArray<KeyEventWrapper*>* events = [NSMutableArray array];
1175 .andDo((^(NSInvocation* invocation) {
1176 FlutterKeyEvent* event;
1177 [invocation getArgument:&event atIndex:2];
1181 __block NSMutableArray<NSDictionary*>* channelEvents = [NSMutableArray array];
1182 OCMStub([binaryMessengerMock sendOnChannel:
@"flutter/keyevent"
1183 message:[OCMArg any]
1184 binaryReply:[OCMArg any]])
1185 .andDo((^(NSInvocation* invocation) {
1187 [invocation getArgument:&data atIndex:3];
1189 [channelEvents addObject:event];
1195 [viewController loadView];
1196 [viewController viewWillAppear];
1199 NSEvent* mouseEvent = flutter::testing::CreateMouseEvent(0x00);
1200 [viewController mouseMoved:mouseEvent];
1201 EXPECT_EQ([events count], 0u);
1205 FlutterKeyEvent* event;
1206 NSDictionary* channelEvent;
1207 NSNumber* logicalKey;
1208 NSNumber* physicalKey;
1209 NSEventModifierFlags flag = [flutter::keyCodeToModifierFlag[keyCode] unsignedLongValue];
1213 flag |= NSEventModifierFlagShift;
1216 flag |= NSEventModifierFlagControl;
1219 flag |= NSEventModifierFlagOption;
1222 flag |= NSEventModifierFlagCommand;
1226 NSEvent* mouseEvent = flutter::testing::CreateMouseEvent(flag);
1227 [viewController mouseMoved:mouseEvent];
1228 EXPECT_EQ([events count], 1u);
1229 event = events[0].data;
1230 logicalKey = [flutter::keyCodeToLogicalKey objectForKey:keyCode];
1231 physicalKey = [flutter::keyCodeToPhysicalKey objectForKey:keyCode];
1232 EXPECT_EQ(event->type, kFlutterKeyEventTypeDown);
1233 EXPECT_EQ(event->logical, logicalKey.unsignedLongLongValue);
1234 EXPECT_EQ(event->physical, physicalKey.unsignedLongLongValue);
1235 EXPECT_EQ(event->synthesized,
true);
1237 channelEvent = channelEvents[0];
1238 EXPECT_TRUE([channelEvent[
@"type"] isEqual:
@"keydown"]);
1239 EXPECT_TRUE([channelEvent[
@"keyCode"] isEqual:keyCode]);
1240 EXPECT_TRUE([channelEvent[
@"modifiers"] isEqual:@(flag)]);
1243 mouseEvent = flutter::testing::CreateMouseEvent(0x00);
1244 [viewController mouseMoved:mouseEvent];
1245 EXPECT_EQ([events count], 2u);
1246 event = events[1].data;
1247 logicalKey = [flutter::keyCodeToLogicalKey objectForKey:keyCode];
1248 physicalKey = [flutter::keyCodeToPhysicalKey objectForKey:keyCode];
1249 EXPECT_EQ(event->type, kFlutterKeyEventTypeUp);
1250 EXPECT_EQ(event->logical, logicalKey.unsignedLongLongValue);
1251 EXPECT_EQ(event->physical, physicalKey.unsignedLongLongValue);
1252 EXPECT_EQ(event->synthesized,
true);
1254 channelEvent = channelEvents[1];
1255 EXPECT_TRUE([channelEvent[
@"type"] isEqual:
@"keyup"]);
1256 EXPECT_TRUE([channelEvent[
@"keyCode"] isEqual:keyCode]);
1257 EXPECT_TRUE([channelEvent[
@"modifiers"] isEqual:@(0)]);
1259 [events removeAllObjects];
1260 [channelEvents removeAllObjects];
1272 OCMStub([engineMock renderer]).andReturn(renderer_);
1277 [viewController loadView];
1278 weakController = viewController;
1280 [engineMock shutDownEngine];
1283 EXPECT_EQ(weakController, nil);