9 #import <OCMock/OCMock.h>
18 #include "flutter/shell/platform/embedder/test_utils/key_codes.g.h"
19 #include "flutter/testing/autoreleasepool_test.h"
20 #include "flutter/testing/testing.h"
22 #pragma mark - Test Helper Classes
29 @property(nonatomic) FlutterKeyEvent*
data;
30 - (nonnull instancetype)initWithEvent:(
const FlutterKeyEvent*)event;
34 - (instancetype)initWithEvent:(const FlutterKeyEvent*)event {
36 _data =
new FlutterKeyEvent(*event);
56 - (instancetype)initWithResponder:(NSResponder*)responder {
57 if (
self = [super init]) {
63 - (void)keyDown:(NSEvent*)event {
64 [_responder keyDown:event];
67 - (void)keyUp:(NSEvent*)event {
68 [_responder keyUp:event];
71 - (BOOL)performKeyEquivalent:(NSEvent*)event {
72 return [_responder performKeyEquivalent:event];
75 - (void)flagsChanged:(NSEvent*)event {
76 [_responder flagsChanged:event];
84 @property(nonatomic, assign) BOOL mouseDownCalled;
85 @property(nonatomic, assign) BOOL mouseUpCalled;
89 - (void)mouseDown:(NSEvent*)event {
93 - (void)mouseUp:(NSEvent*)event {
94 self.mouseUpCalled = YES;
99 - (bool)testKeyEventsAreSentToFramework:(
id)mockEngine;
100 - (bool)testKeyEventsArePropagatedIfNotHandled:(
id)mockEngine;
101 - (bool)testKeyEventsAreNotPropagatedIfHandled:(
id)mockEngine;
102 - (bool)testCtrlTabKeyEventIsPropagated:(
id)mockEngine;
103 - (bool)testKeyEquivalentIsPassedToTextInputPlugin:(
id)mockEngine;
104 - (bool)testFlagsChangedEventsArePropagatedIfNotHandled:(
id)mockEngine;
105 - (bool)testKeyboardIsRestartedOnEngineRestart:(
id)mockEngine;
106 - (bool)testTrackpadGesturesAreSentToFramework:(
id)mockEngine;
107 - (bool)mouseAndGestureEventsAreHandledSeparately:(
id)engineMock;
108 - (bool)testMouseDownUpEventsSentToNextResponder:(
id)mockEngine;
109 - (bool)testModifierKeysAreSynthesizedOnMouseMove:(
id)mockEngine;
110 - (bool)testViewWillAppearCalledMultipleTimes:(
id)mockEngine;
111 - (bool)testFlutterViewIsConfigured:(
id)mockEngine;
116 + (void)respondFalseForSendEvent:(const FlutterKeyEvent&)event
117 callback:(nullable FlutterKeyEventCallback)callback
118 userData:(nullable
void*)userData;
121 #pragma mark - Static helper functions
123 using namespace ::flutter::testing::keycodes;
129 id MockGestureEvent(NSEventType type, NSEventPhase phase,
double magnification,
double rotation) {
130 id event = [OCMockObject mockForClass:[NSEvent class]];
131 NSPoint locationInWindow = NSMakePoint(0, 0);
134 NSTimeInterval timestamp = 1;
135 NSUInteger modifierFlags = 0;
136 [(NSEvent*)[[event stub] andReturnValue:OCMOCK_VALUE(type)] type];
137 [(NSEvent*)[[event stub] andReturnValue:OCMOCK_VALUE(phase)] phase];
138 [(NSEvent*)[[event stub] andReturnValue:OCMOCK_VALUE(locationInWindow)] locationInWindow];
139 [(NSEvent*)[[event stub] andReturnValue:OCMOCK_VALUE(deltaX)] deltaX];
140 [(NSEvent*)[[event stub] andReturnValue:OCMOCK_VALUE(deltaY)] deltaY];
141 [(NSEvent*)[[event stub] andReturnValue:OCMOCK_VALUE(timestamp)] timestamp];
142 [(NSEvent*)[[event stub] andReturnValue:OCMOCK_VALUE(modifierFlags)] modifierFlags];
143 [(NSEvent*)[[event stub] andReturnValue:OCMOCK_VALUE(magnification)] magnification];
144 [(NSEvent*)[[event stub] andReturnValue:OCMOCK_VALUE(rotation)] rotation];
150 NSString* fixtures = @(testing::GetFixturesPath());
152 initWithAssetsPath:fixtures
153 ICUDataPath:[fixtures stringByAppendingString:@"/icudtl.dat"]];
154 return [[
FlutterEngine alloc] initWithName:@"test" project:project allowHeadlessExecution:true];
157 NSResponder* mockResponder() {
158 NSResponder* mock = OCMStrictClassMock([NSResponder
class]);
159 OCMStub([mock keyDown:[OCMArg any]]).andDo(nil);
160 OCMStub([mock keyUp:[OCMArg any]]).andDo(nil);
161 OCMStub([mock flagsChanged:[OCMArg any]]).andDo(nil);
165 NSEvent* CreateMouseEvent(NSEventModifierFlags modifierFlags) {
166 return [NSEvent mouseEventWithType:NSEventTypeMouseMoved
168 modifierFlags:modifierFlags
179 #pragma mark - gtest tests
188 [viewControllerMock loadView];
189 auto subViews = [viewControllerMock.view subviews];
191 EXPECT_EQ([subViews count], 1u);
192 EXPECT_EQ(subViews[0], viewControllerMock.flutterView);
194 NSTextField* textField = [[NSTextField alloc] initWithFrame:NSMakeRect(0, 0, 1, 1)];
195 [viewControllerMock.view addSubview:textField];
197 subViews = [viewControllerMock.view subviews];
198 EXPECT_EQ([subViews count], 2u);
200 auto accessibilityChildren = viewControllerMock.view.accessibilityChildren;
202 EXPECT_EQ([accessibilityChildren count], 1u);
203 EXPECT_EQ(accessibilityChildren[0], viewControllerMock.flutterView);
208 [viewControllerMock loadView];
209 EXPECT_EQ([viewControllerMock.flutterView acceptsFirstMouse:nil], YES);
217 [viewController loadView];
219 NSWindow* window = [[NSWindow alloc] initWithContentRect:NSMakeRect(0, 0, 800, 600)
220 styleMask:NSBorderlessWindowMask
221 backing:NSBackingStoreBuffered
223 window.contentView = viewController.view;
224 NSView* dummyView = [[NSView alloc] initWithFrame:CGRectZero];
225 [viewController.view addSubview:dummyView];
227 [dummyView addSubview:viewController.textInputPlugin];
229 EXPECT_TRUE([window makeFirstResponder:viewController.textInputPlugin]);
230 EXPECT_EQ([window firstResponder], viewController.textInputPlugin);
231 EXPECT_FALSE(viewController.textInputPlugin.superview == viewController.view);
234 EXPECT_TRUE(viewController.textInputPlugin.superview == viewController.view);
238 NSString* fixtures = @(testing::GetFixturesPath());
240 initWithAssetsPath:fixtures
241 ICUDataPath:[fixtures stringByAppendingString:@"/icudtl.dat"]];
244 ASSERT_EQ(viewController.
mouseTrackingMode, kFlutterMouseTrackingModeInActiveApp);
248 id mockEngine = GetMockEngine();
253 id mockEngine = GetMockEngine();
259 id mockEngine = GetMockEngine();
265 id mockEngine = GetMockEngine();
270 id mockEngine = GetMockEngine();
272 testKeyEquivalentIsPassedToTextInputPlugin:mockEngine]);
276 id mockEngine = GetMockEngine();
278 testFlagsChangedEventsArePropagatedIfNotHandled:mockEngine]);
282 id mockEngine = GetMockEngine();
288 id mockEngine = GetMockEngine();
294 id mockEngine = GetMockEngine();
300 id mockEngine = GetMockEngine();
306 id mockEngine = GetMockEngine();
312 id mockEngine = GetMockEngine();
318 id mockEngine = GetMockEngine();
336 #pragma mark - FlutterViewControllerTestObjC
340 - (bool)testKeyEventsAreSentToFramework:(
id)engineMock {
343 [engineMock binaryMessenger])
344 .andReturn(binaryMessengerMock);
349 @selector(respondFalseForSendEvent:callback:userData:));
353 NSDictionary* expectedEvent = @{
354 @"keymap" :
@"macos",
355 @"type" :
@"keydown",
357 @"modifiers" : @(538968064),
358 @"characters" :
@".",
359 @"charactersIgnoringModifiers" :
@".",
362 CGEventRef cgEvent = CGEventCreateKeyboardEvent(NULL, 65, TRUE);
363 NSEvent*
event = [NSEvent eventWithCGEvent:cgEvent];
364 [viewController viewWillAppear];
365 [viewController keyDown:event];
368 [binaryMessengerMock sendOnChannel:
@"flutter/keyevent"
369 message:encodedKeyEvent
370 binaryReply:[OCMArg any]]);
378 - (bool)testCtrlTabKeyEventIsPropagated:(
id)engineMock {
379 __block
bool called =
false;
380 __block FlutterKeyEvent last_event;
384 .andDo((^(NSInvocation* invocation) {
385 FlutterKeyEvent* event;
386 [invocation getArgument:&event atIndex:2];
394 NSEvent*
event = [NSEvent keyEventWithType:NSEventTypeKeyDown
396 modifierFlags:0x40101
401 charactersIgnoringModifiers:@""
404 const uint64_t kPhysicalKeyTab = 0x7002b;
406 [viewController viewWillAppear];
408 NSWindow* window = [[NSWindow alloc] initWithContentRect:NSMakeRect(0, 0, 800, 600)
409 styleMask:NSBorderlessWindowMask
410 backing:NSBackingStoreBuffered
412 window.contentView = viewController.view;
413 [window makeFirstResponder:viewController.flutterView];
414 [viewController.view performKeyEquivalent:event];
417 EXPECT_EQ(last_event.type, kFlutterKeyEventTypeDown);
418 EXPECT_EQ(last_event.physical, kPhysicalKeyTab);
422 - (bool)testKeyEquivalentIsPassedToTextInputPlugin:(
id)engineMock {
423 __block
bool called =
false;
424 __block FlutterKeyEvent last_event;
428 .andDo((^(NSInvocation* invocation) {
429 FlutterKeyEvent* event;
430 [invocation getArgument:&event atIndex:2];
438 NSEvent*
event = [NSEvent keyEventWithType:NSEventTypeKeyDown
440 modifierFlags:0x40101
445 charactersIgnoringModifiers:@""
448 const uint64_t kPhysicalKeyTab = 0x7002b;
450 [viewController viewWillAppear];
452 NSWindow* window = [[NSWindow alloc] initWithContentRect:NSMakeRect(0, 0, 800, 600)
453 styleMask:NSBorderlessWindowMask
454 backing:NSBackingStoreBuffered
456 window.contentView = viewController.view;
458 [viewController.view addSubview:viewController.textInputPlugin];
462 [window makeFirstResponder:viewController.textInputPlugin];
464 [viewController.view performKeyEquivalent:event];
467 EXPECT_EQ(last_event.type, kFlutterKeyEventTypeDown);
468 EXPECT_EQ(last_event.physical, kPhysicalKeyTab);
472 - (bool)testKeyEventsArePropagatedIfNotHandled:(
id)engineMock {
475 [engineMock binaryMessenger])
476 .andReturn(binaryMessengerMock);
481 @selector(respondFalseForSendEvent:callback:userData:));
485 id responderMock = flutter::testing::mockResponder();
487 viewController.nextResponder = responderWrapper;
488 NSDictionary* expectedEvent = @{
489 @"keymap" :
@"macos",
490 @"type" :
@"keydown",
492 @"modifiers" : @(538968064),
493 @"characters" :
@".",
494 @"charactersIgnoringModifiers" :
@".",
497 CGEventRef cgEvent = CGEventCreateKeyboardEvent(NULL, 65, TRUE);
498 NSEvent*
event = [NSEvent eventWithCGEvent:cgEvent];
500 [binaryMessengerMock sendOnChannel:
@"flutter/keyevent"
501 message:encodedKeyEvent
502 binaryReply:[OCMArg any]])
503 .andDo((^(NSInvocation* invocation) {
505 [invocation getArgument:&handler atIndex:4];
506 NSDictionary* reply = @{
507 @"handled" : @(
false),
510 handler(encodedReply);
512 [viewController viewWillAppear];
513 [viewController keyDown:event];
516 [responderMock keyDown:[OCMArg any]]);
518 [binaryMessengerMock sendOnChannel:
@"flutter/keyevent"
519 message:encodedKeyEvent
520 binaryReply:[OCMArg any]]);
527 - (bool)testFlutterViewIsConfigured:(
id)engineMock {
529 OCMStub([engineMock renderer]).andReturn(renderer_);
534 [viewController loadView];
538 OCMVerify([engineMock renderer]);
546 - (bool)testFlagsChangedEventsArePropagatedIfNotHandled:(
id)engineMock {
549 [engineMock binaryMessenger])
550 .andReturn(binaryMessengerMock);
555 @selector(respondFalseForSendEvent:callback:userData:));
559 id responderMock = flutter::testing::mockResponder();
561 viewController.nextResponder = responderWrapper;
562 NSDictionary* expectedEvent = @{
563 @"keymap" :
@"macos",
564 @"type" :
@"keydown",
566 @"modifiers" : @(537001986),
569 CGEventRef cgEvent = CGEventCreateKeyboardEvent(NULL, 56, TRUE);
570 CGEventSetType(cgEvent, kCGEventFlagsChanged);
571 NSEvent*
event = [NSEvent eventWithCGEvent:cgEvent];
573 [binaryMessengerMock sendOnChannel:
@"flutter/keyevent"
574 message:encodedKeyEvent
575 binaryReply:[OCMArg any]])
576 .andDo((^(NSInvocation* invocation) {
578 [invocation getArgument:&handler atIndex:4];
579 NSDictionary* reply = @{
580 @"handled" : @(
false),
583 handler(encodedReply);
585 [viewController viewWillAppear];
586 [viewController flagsChanged:event];
589 [binaryMessengerMock sendOnChannel:
@"flutter/keyevent"
590 message:encodedKeyEvent
591 binaryReply:[OCMArg any]]);
592 }
@catch (NSException* e) {
593 NSLog(
@"%@", e.reason);
599 - (bool)testKeyEventsAreNotPropagatedIfHandled:(
id)engineMock {
602 [engineMock binaryMessenger])
603 .andReturn(binaryMessengerMock);
608 @selector(respondFalseForSendEvent:callback:userData:));
612 id responderMock = flutter::testing::mockResponder();
614 viewController.nextResponder = responderWrapper;
615 NSDictionary* expectedEvent = @{
616 @"keymap" :
@"macos",
617 @"type" :
@"keydown",
619 @"modifiers" : @(538968064),
620 @"characters" :
@".",
621 @"charactersIgnoringModifiers" :
@".",
624 CGEventRef cgEvent = CGEventCreateKeyboardEvent(NULL, 65, TRUE);
625 NSEvent*
event = [NSEvent eventWithCGEvent:cgEvent];
627 [binaryMessengerMock sendOnChannel:
@"flutter/keyevent"
628 message:encodedKeyEvent
629 binaryReply:[OCMArg any]])
630 .andDo((^(NSInvocation* invocation) {
632 [invocation getArgument:&handler atIndex:4];
633 NSDictionary* reply = @{
634 @"handled" : @(
true),
637 handler(encodedReply);
639 [viewController viewWillAppear];
640 [viewController keyDown:event];
643 never(), [responderMock keyDown:[OCMArg any]]);
645 [binaryMessengerMock sendOnChannel:
@"flutter/keyevent"
646 message:encodedKeyEvent
647 binaryReply:[OCMArg any]]);
654 - (bool)testKeyboardIsRestartedOnEngineRestart:(
id)engineMock {
657 [engineMock binaryMessenger])
658 .andReturn(binaryMessengerMock);
659 __block
bool called =
false;
660 __block FlutterKeyEvent last_event;
664 .andDo((^(NSInvocation* invocation) {
665 FlutterKeyEvent* event;
666 [invocation getArgument:&event atIndex:2];
674 [viewController viewWillAppear];
675 NSEvent* keyADown = [NSEvent keyEventWithType:NSEventTypeKeyDown
682 charactersIgnoringModifiers:@"a"
685 const uint64_t kPhysicalKeyA = 0x70004;
692 [viewController keyDown:keyADown];
694 EXPECT_EQ(last_event.type, kFlutterKeyEventTypeDown);
695 EXPECT_EQ(last_event.physical, kPhysicalKeyA);
700 [viewController keyDown:keyADown];
702 EXPECT_EQ(last_event.type, kFlutterKeyEventTypeDown);
703 EXPECT_EQ(last_event.physical, kPhysicalKeyA);
707 + (void)respondFalseForSendEvent:(const FlutterKeyEvent&)event
708 callback:(nullable FlutterKeyEventCallback)callback
709 userData:(nullable
void*)userData {
710 if (callback !=
nullptr) {
711 callback(
false, userData);
715 - (bool)testTrackpadGesturesAreSentToFramework:(
id)engineMock {
718 OCMStub([engineMock renderer]).andReturn(renderer_);
719 __block
bool called =
false;
720 __block FlutterPointerEvent last_event;
722 .andDo((^(NSInvocation* invocation) {
723 FlutterPointerEvent* event;
724 [invocation getArgument:&event atIndex:2];
732 [viewController loadView];
736 CGEventRef cgEventStart = CGEventCreateScrollWheelEvent(NULL, kCGScrollEventUnitPixel, 1, 0);
737 CGEventSetType(cgEventStart, kCGEventScrollWheel);
738 CGEventSetIntegerValueField(cgEventStart, kCGScrollWheelEventScrollPhase, kCGScrollPhaseBegan);
739 CGEventSetIntegerValueField(cgEventStart, kCGScrollWheelEventIsContinuous, 1);
742 [viewController scrollWheel:[NSEvent eventWithCGEvent:cgEventStart]];
744 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
745 EXPECT_EQ(last_event.phase, kPanZoomStart);
746 EXPECT_EQ(last_event.device_kind, kFlutterPointerDeviceKindTrackpad);
747 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
750 CGEventRef cgEventUpdate = CGEventCreateCopy(cgEventStart);
751 CGEventSetIntegerValueField(cgEventUpdate, kCGScrollWheelEventScrollPhase, kCGScrollPhaseChanged);
752 CGEventSetIntegerValueField(cgEventUpdate, kCGScrollWheelEventDeltaAxis2, 1);
753 CGEventSetIntegerValueField(cgEventUpdate, kCGScrollWheelEventDeltaAxis1, 2);
756 [viewController scrollWheel:[NSEvent eventWithCGEvent:cgEventUpdate]];
758 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
759 EXPECT_EQ(last_event.phase, kPanZoomUpdate);
760 EXPECT_EQ(last_event.device_kind, kFlutterPointerDeviceKindTrackpad);
761 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
762 EXPECT_EQ(last_event.pan_x, 8 * viewController.flutterView.layer.contentsScale);
763 EXPECT_EQ(last_event.pan_y, 16 * viewController.flutterView.layer.contentsScale);
767 [viewController scrollWheel:[NSEvent eventWithCGEvent:cgEventUpdate]];
769 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
770 EXPECT_EQ(last_event.phase, kPanZoomUpdate);
771 EXPECT_EQ(last_event.device_kind, kFlutterPointerDeviceKindTrackpad);
772 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
773 EXPECT_EQ(last_event.pan_x, 16 * viewController.flutterView.layer.contentsScale);
774 EXPECT_EQ(last_event.pan_y, 32 * viewController.flutterView.layer.contentsScale);
777 CGEventRef cgEventEnd = CGEventCreateCopy(cgEventStart);
778 CGEventSetIntegerValueField(cgEventEnd, kCGScrollWheelEventScrollPhase, kCGScrollPhaseEnded);
781 [viewController scrollWheel:[NSEvent eventWithCGEvent:cgEventEnd]];
783 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
784 EXPECT_EQ(last_event.phase, kPanZoomEnd);
785 EXPECT_EQ(last_event.device_kind, kFlutterPointerDeviceKindTrackpad);
786 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
789 CGEventRef cgEventMomentumStart = CGEventCreateCopy(cgEventStart);
790 CGEventSetIntegerValueField(cgEventMomentumStart, kCGScrollWheelEventScrollPhase, 0);
791 CGEventSetIntegerValueField(cgEventMomentumStart, kCGScrollWheelEventMomentumPhase,
792 kCGMomentumScrollPhaseBegin);
795 [viewController scrollWheel:[NSEvent eventWithCGEvent:cgEventMomentumStart]];
796 EXPECT_FALSE(called);
799 CGEventRef cgEventMomentumUpdate = CGEventCreateCopy(cgEventStart);
800 CGEventSetIntegerValueField(cgEventMomentumUpdate, kCGScrollWheelEventScrollPhase, 0);
801 CGEventSetIntegerValueField(cgEventMomentumUpdate, kCGScrollWheelEventMomentumPhase,
802 kCGMomentumScrollPhaseContinue);
805 [viewController scrollWheel:[NSEvent eventWithCGEvent:cgEventMomentumUpdate]];
806 EXPECT_FALSE(called);
809 id touchMock = OCMClassMock([NSTouch
class]);
810 NSSet* touchSet = [NSSet setWithObject:touchMock];
811 id touchEventMock1 = OCMClassMock([NSEvent
class]);
812 OCMStub([touchEventMock1 allTouches]).andReturn(touchSet);
813 CGPoint touchLocation = {0, 0};
814 OCMStub([touchEventMock1 locationInWindow]).andReturn(touchLocation);
815 OCMStub([(NSEvent*)touchEventMock1 timestamp]).andReturn(0.150);
819 [viewController touchesBeganWithEvent:touchEventMock1];
820 EXPECT_FALSE(called);
823 id touchEventMock2 = OCMClassMock([NSEvent
class]);
824 OCMStub([touchEventMock2 allTouches]).andReturn(touchSet);
825 OCMStub([touchEventMock2 locationInWindow]).andReturn(touchLocation);
826 OCMStub([(NSEvent*)touchEventMock2 timestamp]).andReturn(0.005);
830 [viewController touchesBeganWithEvent:touchEventMock2];
832 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindScrollInertiaCancel);
833 EXPECT_EQ(last_event.device_kind, kFlutterPointerDeviceKindTrackpad);
836 CGEventRef cgEventMomentumEnd = CGEventCreateCopy(cgEventStart);
837 CGEventSetIntegerValueField(cgEventMomentumEnd, kCGScrollWheelEventScrollPhase, 0);
838 CGEventSetIntegerValueField(cgEventMomentumEnd, kCGScrollWheelEventMomentumPhase,
839 kCGMomentumScrollPhaseEnd);
842 [viewController scrollWheel:[NSEvent eventWithCGEvent:cgEventMomentumEnd]];
843 EXPECT_FALSE(called);
846 CGEventRef cgEventMayBegin = CGEventCreateCopy(cgEventStart);
847 CGEventSetIntegerValueField(cgEventMayBegin, kCGScrollWheelEventScrollPhase,
848 kCGScrollPhaseMayBegin);
851 [viewController scrollWheel:[NSEvent eventWithCGEvent:cgEventMayBegin]];
853 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
854 EXPECT_EQ(last_event.phase, kPanZoomStart);
855 EXPECT_EQ(last_event.device_kind, kFlutterPointerDeviceKindTrackpad);
856 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
859 CGEventRef cgEventCancel = CGEventCreateCopy(cgEventStart);
860 CGEventSetIntegerValueField(cgEventCancel, kCGScrollWheelEventScrollPhase,
861 kCGScrollPhaseCancelled);
864 [viewController scrollWheel:[NSEvent eventWithCGEvent:cgEventCancel]];
866 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
867 EXPECT_EQ(last_event.phase, kPanZoomEnd);
868 EXPECT_EQ(last_event.device_kind, kFlutterPointerDeviceKindTrackpad);
869 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
872 CGEventRef cgEventDiscrete = CGEventCreateScrollWheelEvent(NULL, kCGScrollEventUnitPixel, 1, 0);
873 CGEventSetType(cgEventDiscrete, kCGEventScrollWheel);
874 CGEventSetIntegerValueField(cgEventDiscrete, kCGScrollWheelEventIsContinuous, 0);
875 CGEventSetIntegerValueField(cgEventDiscrete, kCGScrollWheelEventDeltaAxis2, 1);
876 CGEventSetIntegerValueField(cgEventDiscrete, kCGScrollWheelEventDeltaAxis1, 2);
879 [viewController scrollWheel:[NSEvent eventWithCGEvent:cgEventDiscrete]];
881 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindScroll);
883 EXPECT_EQ(last_event.scroll_delta_x, -40 * viewController.flutterView.layer.contentsScale);
884 EXPECT_EQ(last_event.scroll_delta_y, -80 * viewController.flutterView.layer.contentsScale);
888 CGEventRef cgEventDiscreteShift =
889 CGEventCreateScrollWheelEvent(NULL, kCGScrollEventUnitPixel, 1, 0);
890 CGEventSetType(cgEventDiscreteShift, kCGEventScrollWheel);
892 CGEventSetIntegerValueField(cgEventDiscreteShift, kCGScrollWheelEventIsContinuous, 0);
893 CGEventSetIntegerValueField(cgEventDiscreteShift, kCGScrollWheelEventDeltaAxis2,
895 CGEventSetIntegerValueField(cgEventDiscreteShift, kCGScrollWheelEventDeltaAxis1,
899 [viewController scrollWheel:[NSEvent eventWithCGEvent:cgEventDiscreteShift]];
901 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindScroll);
903 EXPECT_FLOAT_EQ(last_event.scroll_delta_x, 0.0 * viewController.flutterView.layer.contentsScale);
904 EXPECT_FLOAT_EQ(last_event.scroll_delta_y,
905 -80.0 * viewController.flutterView.layer.contentsScale);
910 [viewController magnifyWithEvent:flutter::testing::MockGestureEvent(NSEventTypeMagnify,
911 NSEventPhaseBegan, 1, 0)];
913 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
914 EXPECT_EQ(last_event.phase, kPanZoomStart);
915 EXPECT_EQ(last_event.device_kind, kFlutterPointerDeviceKindTrackpad);
916 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
920 [viewController magnifyWithEvent:flutter::testing::MockGestureEvent(NSEventTypeMagnify,
921 NSEventPhaseChanged, 1, 0)];
923 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
924 EXPECT_EQ(last_event.phase, kPanZoomUpdate);
925 EXPECT_EQ(last_event.device_kind, kFlutterPointerDeviceKindTrackpad);
926 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
927 EXPECT_EQ(last_event.pan_x, 0);
928 EXPECT_EQ(last_event.pan_y, 0);
929 EXPECT_EQ(last_event.scale, 2);
931 EXPECT_EQ(last_event.rotation, 0);
935 [viewController magnifyWithEvent:flutter::testing::MockGestureEvent(NSEventTypeMagnify,
936 NSEventPhaseChanged, 1, 0)];
938 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
939 EXPECT_EQ(last_event.phase, kPanZoomUpdate);
940 EXPECT_EQ(last_event.device_kind, kFlutterPointerDeviceKindTrackpad);
941 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
942 EXPECT_EQ(last_event.pan_x, 0);
943 EXPECT_EQ(last_event.pan_y, 0);
944 EXPECT_EQ(last_event.scale, 4);
946 EXPECT_EQ(last_event.rotation, 0);
950 [viewController magnifyWithEvent:flutter::testing::MockGestureEvent(NSEventTypeMagnify,
951 NSEventPhaseEnded, 0, 0)];
953 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
954 EXPECT_EQ(last_event.phase, kPanZoomEnd);
955 EXPECT_EQ(last_event.device_kind, kFlutterPointerDeviceKindTrackpad);
956 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
961 [viewController rotateWithEvent:flutter::testing::MockGestureEvent(NSEventTypeRotate,
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 rotateWithEvent:flutter::testing::MockGestureEvent(
972 NSEventTypeRotate, NSEventPhaseChanged, 0, -180)];
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, 1);
981 EXPECT_EQ(last_event.rotation, M_PI);
985 [viewController rotateWithEvent:flutter::testing::MockGestureEvent(
986 NSEventTypeRotate, NSEventPhaseChanged, 0, -360)];
988 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
989 EXPECT_EQ(last_event.phase, kPanZoomUpdate);
990 EXPECT_EQ(last_event.device_kind, kFlutterPointerDeviceKindTrackpad);
991 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
992 EXPECT_EQ(last_event.pan_x, 0);
993 EXPECT_EQ(last_event.pan_y, 0);
994 EXPECT_EQ(last_event.scale, 1);
995 EXPECT_EQ(last_event.rotation, 3 * M_PI);
999 [viewController rotateWithEvent:flutter::testing::MockGestureEvent(NSEventTypeRotate,
1000 NSEventPhaseEnded, 0, 0)];
1001 EXPECT_TRUE(called);
1002 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
1003 EXPECT_EQ(last_event.phase, kPanZoomEnd);
1004 EXPECT_EQ(last_event.device_kind, kFlutterPointerDeviceKindTrackpad);
1005 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
1009 [viewController rotateWithEvent:flutter::testing::MockGestureEvent(NSEventTypeRotate,
1010 NSEventPhaseCancelled, 0, 0)];
1011 EXPECT_FALSE(called);
1017 - (bool)mouseAndGestureEventsAreHandledSeparately:(
id)engineMock {
1021 [viewController loadView];
1025 CGEventRef cgEventStart = CGEventCreateScrollWheelEvent(NULL, kCGScrollEventUnitPixel, 1, 0);
1026 CGEventSetType(cgEventStart, kCGEventScrollWheel);
1027 CGEventSetIntegerValueField(cgEventStart, kCGScrollWheelEventScrollPhase, kCGScrollPhaseBegan);
1028 CGEventSetIntegerValueField(cgEventStart, kCGScrollWheelEventIsContinuous, 1);
1029 [viewController scrollWheel:[NSEvent eventWithCGEvent:cgEventStart]];
1030 CFRelease(cgEventStart);
1032 CGEventRef cgEventUpdate = CGEventCreateCopy(cgEventStart);
1033 CGEventSetIntegerValueField(cgEventUpdate, kCGScrollWheelEventScrollPhase, kCGScrollPhaseChanged);
1034 CGEventSetIntegerValueField(cgEventUpdate, kCGScrollWheelEventDeltaAxis2, 1);
1035 CGEventSetIntegerValueField(cgEventUpdate, kCGScrollWheelEventDeltaAxis1, 2);
1036 [viewController scrollWheel:[NSEvent eventWithCGEvent:cgEventUpdate]];
1037 CFRelease(cgEventUpdate);
1039 NSEvent* mouseEvent = flutter::testing::CreateMouseEvent(0x00);
1040 [viewController mouseEntered:mouseEvent];
1041 [viewController mouseExited:mouseEvent];
1044 CGEventRef cgEventEnd = CGEventCreateCopy(cgEventStart);
1045 CGEventSetIntegerValueField(cgEventEnd, kCGScrollWheelEventScrollPhase, kCGScrollPhaseEnded);
1046 [viewController scrollWheel:[NSEvent eventWithCGEvent:cgEventEnd]];
1047 CFRelease(cgEventEnd);
1052 - (bool)testViewWillAppearCalledMultipleTimes:(
id)engineMock {
1056 [viewController viewWillAppear];
1057 [viewController viewWillAppear];
1065 [key isEqualToString:
@"Contents/Frameworks/App.framework/Resources/flutter_assets/test.png"]);
1073 EXPECT_TRUE([packageKey
1075 @"Contents/Frameworks/App.framework/Resources/flutter_assets/packages/test/test.png"]);
1079 static void SwizzledNoop(
id self,
SEL _cmd) {}
1091 - (bool)testMouseDownUpEventsSentToNextResponder:(
id)engineMock {
1092 if (@available(macOS 13.3.1, *)) {
1102 Method mouseDown = class_getInstanceMethod([NSResponder
class],
@selector(mouseDown:));
1103 Method mouseUp = class_getInstanceMethod([NSResponder
class],
@selector(mouseUp:));
1104 IMP noopImp = (IMP)SwizzledNoop;
1105 IMP origMouseDown = method_setImplementation(mouseDown, noopImp);
1106 IMP origMouseUp = method_setImplementation(mouseUp, noopImp);
1116 NSEvent* mouseEvent = flutter::testing::CreateMouseEvent(0x00);
1117 [view mouseDown:mouseEvent];
1122 [view mouseUp:mouseEvent];
1127 method_setImplementation(mouseDown, origMouseDown);
1128 method_setImplementation(mouseUp, origMouseUp);
1133 - (bool)testModifierKeysAreSynthesizedOnMouseMove:(
id)engineMock {
1136 [engineMock binaryMessenger])
1137 .andReturn(binaryMessengerMock);
1141 OCMStub([engineMock renderer]).andReturn(renderer_);
1144 __block NSMutableArray<KeyEventWrapper*>* events = [NSMutableArray array];
1148 .andDo((^(NSInvocation* invocation) {
1149 FlutterKeyEvent* event;
1150 [invocation getArgument:&event atIndex:2];
1154 __block NSMutableArray<NSDictionary*>* channelEvents = [NSMutableArray array];
1155 OCMStub([binaryMessengerMock sendOnChannel:
@"flutter/keyevent"
1156 message:[OCMArg any]
1157 binaryReply:[OCMArg any]])
1158 .andDo((^(NSInvocation* invocation) {
1160 [invocation getArgument:&data atIndex:3];
1162 [channelEvents addObject:event];
1168 [viewController loadView];
1169 [viewController viewWillAppear];
1172 NSEvent* mouseEvent = flutter::testing::CreateMouseEvent(0x00);
1173 [viewController mouseMoved:mouseEvent];
1174 EXPECT_EQ([events count], 0u);
1178 FlutterKeyEvent* event;
1179 NSDictionary* channelEvent;
1180 NSNumber* logicalKey;
1181 NSNumber* physicalKey;
1182 NSEventModifierFlags flag = [flutter::keyCodeToModifierFlag[keyCode] unsignedLongValue];
1186 flag |= NSEventModifierFlagShift;
1189 flag |= NSEventModifierFlagControl;
1192 flag |= NSEventModifierFlagOption;
1195 flag |= NSEventModifierFlagCommand;
1199 NSEvent* mouseEvent = flutter::testing::CreateMouseEvent(flag);
1200 [viewController mouseMoved:mouseEvent];
1201 EXPECT_EQ([events count], 1u);
1202 event = events[0].data;
1203 logicalKey = [flutter::keyCodeToLogicalKey objectForKey:keyCode];
1204 physicalKey = [flutter::keyCodeToPhysicalKey objectForKey:keyCode];
1205 EXPECT_EQ(event->type, kFlutterKeyEventTypeDown);
1206 EXPECT_EQ(event->logical, logicalKey.unsignedLongLongValue);
1207 EXPECT_EQ(event->physical, physicalKey.unsignedLongLongValue);
1208 EXPECT_EQ(event->synthesized,
true);
1210 channelEvent = channelEvents[0];
1211 EXPECT_TRUE([channelEvent[
@"type"] isEqual:
@"keydown"]);
1212 EXPECT_TRUE([channelEvent[
@"keyCode"] isEqual:keyCode]);
1213 EXPECT_TRUE([channelEvent[
@"modifiers"] isEqual:@(flag)]);
1216 mouseEvent = flutter::testing::CreateMouseEvent(0x00);
1217 [viewController mouseMoved:mouseEvent];
1218 EXPECT_EQ([events count], 2u);
1219 event = events[1].data;
1220 logicalKey = [flutter::keyCodeToLogicalKey objectForKey:keyCode];
1221 physicalKey = [flutter::keyCodeToPhysicalKey objectForKey:keyCode];
1222 EXPECT_EQ(event->type, kFlutterKeyEventTypeUp);
1223 EXPECT_EQ(event->logical, logicalKey.unsignedLongLongValue);
1224 EXPECT_EQ(event->physical, physicalKey.unsignedLongLongValue);
1225 EXPECT_EQ(event->synthesized,
true);
1227 channelEvent = channelEvents[1];
1228 EXPECT_TRUE([channelEvent[
@"type"] isEqual:
@"keyup"]);
1229 EXPECT_TRUE([channelEvent[
@"keyCode"] isEqual:keyCode]);
1230 EXPECT_TRUE([channelEvent[
@"modifiers"] isEqual:@(0)]);
1232 [events removeAllObjects];
1233 [channelEvents removeAllObjects];
1245 OCMStub([engineMock renderer]).andReturn(renderer_);
1250 [viewController loadView];
1251 weakController = viewController;
1253 [engineMock shutDownEngine];
1256 EXPECT_EQ(weakController, nil);