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
26 @property(nonatomic) FlutterKeyEvent*
data;
27 - (nonnull instancetype)initWithEvent:(
const FlutterKeyEvent*)event;
31 - (instancetype)initWithEvent:(const FlutterKeyEvent*)event {
33 _data =
new FlutterKeyEvent(*event);
53 - (instancetype)initWithResponder:(NSResponder*)responder {
54 if (
self = [super init]) {
60 - (void)keyDown:(NSEvent*)event {
61 [_responder keyDown:event];
64 - (void)keyUp:(NSEvent*)event {
65 [_responder keyUp:event];
68 - (BOOL)performKeyEquivalent:(NSEvent*)event {
69 return [_responder performKeyEquivalent:event];
72 - (void)flagsChanged:(NSEvent*)event {
73 [_responder flagsChanged:event];
81 @property(nonatomic, assign) BOOL mouseDownCalled;
82 @property(nonatomic, assign) BOOL mouseUpCalled;
86 - (void)mouseDown:(NSEvent*)event {
90 - (void)mouseUp:(NSEvent*)event {
91 self.mouseUpCalled = YES;
96 - (bool)testKeyEventsAreSentToFramework:(
id)mockEngine;
97 - (bool)testKeyEventsArePropagatedIfNotHandled:(
id)mockEngine;
98 - (bool)testKeyEventsAreNotPropagatedIfHandled:(
id)mockEngine;
99 - (bool)testCtrlTabKeyEventIsPropagated:(
id)mockEngine;
100 - (bool)testKeyEquivalentIsPassedToTextInputPlugin:(
id)mockEngine;
101 - (bool)testFlagsChangedEventsArePropagatedIfNotHandled:(
id)mockEngine;
102 - (bool)testKeyboardIsRestartedOnEngineRestart:(
id)mockEngine;
103 - (bool)testTrackpadGesturesAreSentToFramework:(
id)mockEngine;
104 - (bool)mouseAndGestureEventsAreHandledSeparately:(
id)engineMock;
105 - (bool)testMouseDownUpEventsSentToNextResponder:(
id)mockEngine;
106 - (bool)testModifierKeysAreSynthesizedOnMouseMove:(
id)mockEngine;
107 - (bool)testViewWillAppearCalledMultipleTimes:(
id)mockEngine;
108 - (bool)testFlutterViewIsConfigured:(
id)mockEngine;
113 + (void)respondFalseForSendEvent:(const FlutterKeyEvent&)event
114 callback:(nullable FlutterKeyEventCallback)callback
115 userData:(nullable
void*)userData;
118 #pragma mark - Static helper functions
120 using namespace ::flutter::testing::keycodes;
126 id MockGestureEvent(NSEventType type, NSEventPhase phase,
double magnification,
double rotation) {
127 id event = [OCMockObject mockForClass:[NSEvent class]];
128 NSPoint locationInWindow = NSMakePoint(0, 0);
131 NSTimeInterval timestamp = 1;
132 NSUInteger modifierFlags = 0;
133 [(NSEvent*)[[event stub] andReturnValue:OCMOCK_VALUE(type)] type];
134 [(NSEvent*)[[event stub] andReturnValue:OCMOCK_VALUE(phase)] phase];
135 [(NSEvent*)[[event stub] andReturnValue:OCMOCK_VALUE(locationInWindow)] locationInWindow];
136 [(NSEvent*)[[event stub] andReturnValue:OCMOCK_VALUE(deltaX)] deltaX];
137 [(NSEvent*)[[event stub] andReturnValue:OCMOCK_VALUE(deltaY)] deltaY];
138 [(NSEvent*)[[event stub] andReturnValue:OCMOCK_VALUE(timestamp)] timestamp];
139 [(NSEvent*)[[event stub] andReturnValue:OCMOCK_VALUE(modifierFlags)] modifierFlags];
140 [(NSEvent*)[[event stub] andReturnValue:OCMOCK_VALUE(magnification)] magnification];
141 [(NSEvent*)[[event stub] andReturnValue:OCMOCK_VALUE(rotation)] rotation];
147 NSString* fixtures = @(testing::GetFixturesPath());
149 initWithAssetsPath:fixtures
150 ICUDataPath:[fixtures stringByAppendingString:@"/icudtl.dat"]];
151 return [[
FlutterEngine alloc] initWithName:@"test" project:project allowHeadlessExecution:true];
154 NSResponder* mockResponder() {
155 NSResponder* mock = OCMStrictClassMock([NSResponder
class]);
156 OCMStub([mock keyDown:[OCMArg any]]).andDo(nil);
157 OCMStub([mock keyUp:[OCMArg any]]).andDo(nil);
158 OCMStub([mock flagsChanged:[OCMArg any]]).andDo(nil);
162 NSEvent* CreateMouseEvent(NSEventModifierFlags modifierFlags) {
163 return [NSEvent mouseEventWithType:NSEventTypeMouseMoved
165 modifierFlags:modifierFlags
176 #pragma mark - gtest tests
185 [viewControllerMock loadView];
186 auto subViews = [viewControllerMock.view subviews];
188 EXPECT_EQ([subViews count], 1u);
189 EXPECT_EQ(subViews[0], viewControllerMock.flutterView);
191 NSTextField* textField = [[NSTextField alloc] initWithFrame:NSMakeRect(0, 0, 1, 1)];
192 [viewControllerMock.view addSubview:textField];
194 subViews = [viewControllerMock.view subviews];
195 EXPECT_EQ([subViews count], 2u);
197 auto accessibilityChildren = viewControllerMock.view.accessibilityChildren;
199 EXPECT_EQ([accessibilityChildren count], 1u);
200 EXPECT_EQ(accessibilityChildren[0], viewControllerMock.flutterView);
205 [viewControllerMock loadView];
206 EXPECT_EQ([viewControllerMock.flutterView acceptsFirstMouse:nil], YES);
214 [viewController loadView];
216 NSWindow* window = [[NSWindow alloc] initWithContentRect:NSMakeRect(0, 0, 800, 600)
217 styleMask:NSBorderlessWindowMask
218 backing:NSBackingStoreBuffered
220 window.contentView = viewController.view;
221 NSView* dummyView = [[NSView alloc] initWithFrame:CGRectZero];
222 [viewController.view addSubview:dummyView];
224 [dummyView addSubview:viewController.textInputPlugin];
226 EXPECT_TRUE([window makeFirstResponder:viewController.textInputPlugin]);
227 EXPECT_EQ([window firstResponder], viewController.textInputPlugin);
228 EXPECT_FALSE(viewController.textInputPlugin.superview == viewController.view);
231 EXPECT_TRUE(viewController.textInputPlugin.superview == viewController.view);
235 NSString* fixtures = @(testing::GetFixturesPath());
237 initWithAssetsPath:fixtures
238 ICUDataPath:[fixtures stringByAppendingString:@"/icudtl.dat"]];
241 ASSERT_EQ(viewController.
mouseTrackingMode, kFlutterMouseTrackingModeInActiveApp);
245 id mockEngine = GetMockEngine();
250 id mockEngine = GetMockEngine();
256 id mockEngine = GetMockEngine();
262 id mockEngine = GetMockEngine();
267 id mockEngine = GetMockEngine();
269 testKeyEquivalentIsPassedToTextInputPlugin:mockEngine]);
273 id mockEngine = GetMockEngine();
275 testFlagsChangedEventsArePropagatedIfNotHandled:mockEngine]);
279 id mockEngine = GetMockEngine();
285 id mockEngine = GetMockEngine();
291 id mockEngine = GetMockEngine();
297 id mockEngine = GetMockEngine();
303 id mockEngine = GetMockEngine();
309 id mockEngine = GetMockEngine();
315 id mockEngine = GetMockEngine();
333 #pragma mark - FlutterViewControllerTestObjC
337 - (bool)testKeyEventsAreSentToFramework:(
id)engineMock {
340 [engineMock binaryMessenger])
341 .andReturn(binaryMessengerMock);
342 OCMStub([[engineMock ignoringNonObjectArgs] sendKeyEvent:FlutterKeyEvent {}
346 @selector(respondFalseForSendEvent:callback:userData:));
350 NSDictionary* expectedEvent = @{
351 @"keymap" :
@"macos",
352 @"type" :
@"keydown",
354 @"modifiers" : @(538968064),
355 @"characters" :
@".",
356 @"charactersIgnoringModifiers" :
@".",
359 CGEventRef cgEvent = CGEventCreateKeyboardEvent(NULL, 65, TRUE);
360 NSEvent*
event = [NSEvent eventWithCGEvent:cgEvent];
361 [viewController viewWillAppear];
362 [viewController keyDown:event];
365 [binaryMessengerMock sendOnChannel:
@"flutter/keyevent"
366 message:encodedKeyEvent
367 binaryReply:[OCMArg any]]);
375 - (bool)testCtrlTabKeyEventIsPropagated:(
id)engineMock {
376 __block
bool called =
false;
377 __block FlutterKeyEvent last_event;
378 OCMStub([[engineMock ignoringNonObjectArgs] sendKeyEvent:FlutterKeyEvent {}
381 .andDo((^(NSInvocation* invocation) {
382 FlutterKeyEvent* event;
383 [invocation getArgument:&event atIndex:2];
391 NSEvent*
event = [NSEvent keyEventWithType:NSEventTypeKeyDown
393 modifierFlags:0x40101
398 charactersIgnoringModifiers:@""
401 const uint64_t kPhysicalKeyTab = 0x7002b;
403 [viewController viewWillAppear];
405 NSWindow* window = [[NSWindow alloc] initWithContentRect:NSMakeRect(0, 0, 800, 600)
406 styleMask:NSBorderlessWindowMask
407 backing:NSBackingStoreBuffered
409 window.contentView = viewController.view;
410 [window makeFirstResponder:viewController.flutterView];
411 [viewController.view performKeyEquivalent:event];
414 EXPECT_EQ(last_event.type, kFlutterKeyEventTypeDown);
415 EXPECT_EQ(last_event.physical, kPhysicalKeyTab);
419 - (bool)testKeyEquivalentIsPassedToTextInputPlugin:(
id)engineMock {
420 __block
bool called =
false;
421 __block FlutterKeyEvent last_event;
422 OCMStub([[engineMock ignoringNonObjectArgs] sendKeyEvent:FlutterKeyEvent {}
425 .andDo((^(NSInvocation* invocation) {
426 FlutterKeyEvent* event;
427 [invocation getArgument:&event atIndex:2];
435 NSEvent*
event = [NSEvent keyEventWithType:NSEventTypeKeyDown
437 modifierFlags:0x40101
442 charactersIgnoringModifiers:@""
445 const uint64_t kPhysicalKeyTab = 0x7002b;
447 [viewController viewWillAppear];
449 NSWindow* window = [[NSWindow alloc] initWithContentRect:NSMakeRect(0, 0, 800, 600)
450 styleMask:NSBorderlessWindowMask
451 backing:NSBackingStoreBuffered
453 window.contentView = viewController.view;
455 [viewController.view addSubview:viewController.textInputPlugin];
459 [window makeFirstResponder:viewController.textInputPlugin];
461 [viewController.view performKeyEquivalent:event];
464 EXPECT_EQ(last_event.type, kFlutterKeyEventTypeDown);
465 EXPECT_EQ(last_event.physical, kPhysicalKeyTab);
469 - (bool)testKeyEventsArePropagatedIfNotHandled:(
id)engineMock {
472 [engineMock binaryMessenger])
473 .andReturn(binaryMessengerMock);
474 OCMStub([[engineMock ignoringNonObjectArgs] sendKeyEvent:FlutterKeyEvent {}
478 @selector(respondFalseForSendEvent:callback:userData:));
482 id responderMock = flutter::testing::mockResponder();
484 viewController.nextResponder = responderWrapper;
485 NSDictionary* expectedEvent = @{
486 @"keymap" :
@"macos",
487 @"type" :
@"keydown",
489 @"modifiers" : @(538968064),
490 @"characters" :
@".",
491 @"charactersIgnoringModifiers" :
@".",
494 CGEventRef cgEvent = CGEventCreateKeyboardEvent(NULL, 65, TRUE);
495 NSEvent*
event = [NSEvent eventWithCGEvent:cgEvent];
497 [binaryMessengerMock sendOnChannel:
@"flutter/keyevent"
498 message:encodedKeyEvent
499 binaryReply:[OCMArg any]])
500 .andDo((^(NSInvocation* invocation) {
502 [invocation getArgument:&handler atIndex:4];
503 NSDictionary* reply = @{
504 @"handled" : @(
false),
507 handler(encodedReply);
509 [viewController viewWillAppear];
510 [viewController keyDown:event];
513 [responderMock keyDown:[OCMArg any]]);
515 [binaryMessengerMock sendOnChannel:
@"flutter/keyevent"
516 message:encodedKeyEvent
517 binaryReply:[OCMArg any]]);
524 - (bool)testFlutterViewIsConfigured:(
id)engineMock {
526 OCMStub([engineMock renderer]).andReturn(renderer_);
531 [viewController loadView];
535 OCMVerify([engineMock renderer]);
543 - (bool)testFlagsChangedEventsArePropagatedIfNotHandled:(
id)engineMock {
546 [engineMock binaryMessenger])
547 .andReturn(binaryMessengerMock);
548 OCMStub([[engineMock ignoringNonObjectArgs] sendKeyEvent:FlutterKeyEvent {}
552 @selector(respondFalseForSendEvent:callback:userData:));
556 id responderMock = flutter::testing::mockResponder();
558 viewController.nextResponder = responderWrapper;
559 NSDictionary* expectedEvent = @{
560 @"keymap" :
@"macos",
561 @"type" :
@"keydown",
563 @"modifiers" : @(537001986),
566 CGEventRef cgEvent = CGEventCreateKeyboardEvent(NULL, 56, TRUE);
567 CGEventSetType(cgEvent, kCGEventFlagsChanged);
568 NSEvent*
event = [NSEvent eventWithCGEvent:cgEvent];
570 [binaryMessengerMock sendOnChannel:
@"flutter/keyevent"
571 message:encodedKeyEvent
572 binaryReply:[OCMArg any]])
573 .andDo((^(NSInvocation* invocation) {
575 [invocation getArgument:&handler atIndex:4];
576 NSDictionary* reply = @{
577 @"handled" : @(
false),
580 handler(encodedReply);
582 [viewController viewWillAppear];
583 [viewController flagsChanged:event];
586 [binaryMessengerMock sendOnChannel:
@"flutter/keyevent"
587 message:encodedKeyEvent
588 binaryReply:[OCMArg any]]);
589 }
@catch (NSException* e) {
590 NSLog(
@"%@", e.reason);
596 - (bool)testKeyEventsAreNotPropagatedIfHandled:(
id)engineMock {
599 [engineMock binaryMessenger])
600 .andReturn(binaryMessengerMock);
601 OCMStub([[engineMock ignoringNonObjectArgs] sendKeyEvent:FlutterKeyEvent {}
605 @selector(respondFalseForSendEvent:callback:userData:));
609 id responderMock = flutter::testing::mockResponder();
611 viewController.nextResponder = responderWrapper;
612 NSDictionary* expectedEvent = @{
613 @"keymap" :
@"macos",
614 @"type" :
@"keydown",
616 @"modifiers" : @(538968064),
617 @"characters" :
@".",
618 @"charactersIgnoringModifiers" :
@".",
621 CGEventRef cgEvent = CGEventCreateKeyboardEvent(NULL, 65, TRUE);
622 NSEvent*
event = [NSEvent eventWithCGEvent:cgEvent];
624 [binaryMessengerMock sendOnChannel:
@"flutter/keyevent"
625 message:encodedKeyEvent
626 binaryReply:[OCMArg any]])
627 .andDo((^(NSInvocation* invocation) {
629 [invocation getArgument:&handler atIndex:4];
630 NSDictionary* reply = @{
631 @"handled" : @(
true),
634 handler(encodedReply);
636 [viewController viewWillAppear];
637 [viewController keyDown:event];
640 never(), [responderMock keyDown:[OCMArg any]]);
642 [binaryMessengerMock sendOnChannel:
@"flutter/keyevent"
643 message:encodedKeyEvent
644 binaryReply:[OCMArg any]]);
651 - (bool)testKeyboardIsRestartedOnEngineRestart:(
id)engineMock {
654 [engineMock binaryMessenger])
655 .andReturn(binaryMessengerMock);
656 __block
bool called =
false;
657 __block FlutterKeyEvent last_event;
658 OCMStub([[engineMock ignoringNonObjectArgs] sendKeyEvent:FlutterKeyEvent {}
661 .andDo((^(NSInvocation* invocation) {
662 FlutterKeyEvent* event;
663 [invocation getArgument:&event atIndex:2];
671 [viewController viewWillAppear];
672 NSEvent* keyADown = [NSEvent keyEventWithType:NSEventTypeKeyDown
679 charactersIgnoringModifiers:@"a"
682 const uint64_t kPhysicalKeyA = 0x70004;
689 [viewController keyDown:keyADown];
691 EXPECT_EQ(last_event.type, kFlutterKeyEventTypeDown);
692 EXPECT_EQ(last_event.physical, kPhysicalKeyA);
697 [viewController keyDown:keyADown];
699 EXPECT_EQ(last_event.type, kFlutterKeyEventTypeDown);
700 EXPECT_EQ(last_event.physical, kPhysicalKeyA);
704 + (void)respondFalseForSendEvent:(const FlutterKeyEvent&)event
705 callback:(nullable FlutterKeyEventCallback)callback
706 userData:(nullable
void*)userData {
707 if (callback !=
nullptr) {
708 callback(
false, userData);
712 - (bool)testTrackpadGesturesAreSentToFramework:(
id)engineMock {
715 OCMStub([engineMock renderer]).andReturn(renderer_);
716 __block
bool called =
false;
717 __block FlutterPointerEvent last_event;
718 OCMStub([[engineMock ignoringNonObjectArgs] sendPointerEvent:FlutterPointerEvent{}])
719 .andDo((^(NSInvocation* invocation) {
720 FlutterPointerEvent* event;
721 [invocation getArgument:&event atIndex:2];
729 [viewController loadView];
733 CGEventRef cgEventStart = CGEventCreateScrollWheelEvent(NULL, kCGScrollEventUnitPixel, 1, 0);
734 CGEventSetType(cgEventStart, kCGEventScrollWheel);
735 CGEventSetIntegerValueField(cgEventStart, kCGScrollWheelEventScrollPhase, kCGScrollPhaseBegan);
736 CGEventSetIntegerValueField(cgEventStart, kCGScrollWheelEventIsContinuous, 1);
739 [viewController scrollWheel:[NSEvent eventWithCGEvent:cgEventStart]];
741 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
742 EXPECT_EQ(last_event.phase, kPanZoomStart);
743 EXPECT_EQ(last_event.device_kind, kFlutterPointerDeviceKindTrackpad);
744 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
747 CGEventRef cgEventUpdate = CGEventCreateCopy(cgEventStart);
748 CGEventSetIntegerValueField(cgEventUpdate, kCGScrollWheelEventScrollPhase, kCGScrollPhaseChanged);
749 CGEventSetIntegerValueField(cgEventUpdate, kCGScrollWheelEventDeltaAxis2, 1);
750 CGEventSetIntegerValueField(cgEventUpdate, kCGScrollWheelEventDeltaAxis1, 2);
753 [viewController scrollWheel:[NSEvent eventWithCGEvent:cgEventUpdate]];
755 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
756 EXPECT_EQ(last_event.phase, kPanZoomUpdate);
757 EXPECT_EQ(last_event.device_kind, kFlutterPointerDeviceKindTrackpad);
758 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
759 EXPECT_EQ(last_event.pan_x, 8 * viewController.flutterView.layer.contentsScale);
760 EXPECT_EQ(last_event.pan_y, 16 * viewController.flutterView.layer.contentsScale);
764 [viewController scrollWheel:[NSEvent eventWithCGEvent:cgEventUpdate]];
766 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
767 EXPECT_EQ(last_event.phase, kPanZoomUpdate);
768 EXPECT_EQ(last_event.device_kind, kFlutterPointerDeviceKindTrackpad);
769 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
770 EXPECT_EQ(last_event.pan_x, 16 * viewController.flutterView.layer.contentsScale);
771 EXPECT_EQ(last_event.pan_y, 32 * viewController.flutterView.layer.contentsScale);
774 CGEventRef cgEventEnd = CGEventCreateCopy(cgEventStart);
775 CGEventSetIntegerValueField(cgEventEnd, kCGScrollWheelEventScrollPhase, kCGScrollPhaseEnded);
778 [viewController scrollWheel:[NSEvent eventWithCGEvent:cgEventEnd]];
780 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
781 EXPECT_EQ(last_event.phase, kPanZoomEnd);
782 EXPECT_EQ(last_event.device_kind, kFlutterPointerDeviceKindTrackpad);
783 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
786 CGEventRef cgEventMomentumStart = CGEventCreateCopy(cgEventStart);
787 CGEventSetIntegerValueField(cgEventMomentumStart, kCGScrollWheelEventScrollPhase, 0);
788 CGEventSetIntegerValueField(cgEventMomentumStart, kCGScrollWheelEventMomentumPhase,
789 kCGMomentumScrollPhaseBegin);
792 [viewController scrollWheel:[NSEvent eventWithCGEvent:cgEventMomentumStart]];
793 EXPECT_FALSE(called);
796 CGEventRef cgEventMomentumUpdate = CGEventCreateCopy(cgEventStart);
797 CGEventSetIntegerValueField(cgEventMomentumUpdate, kCGScrollWheelEventScrollPhase, 0);
798 CGEventSetIntegerValueField(cgEventMomentumUpdate, kCGScrollWheelEventMomentumPhase,
799 kCGMomentumScrollPhaseContinue);
802 [viewController scrollWheel:[NSEvent eventWithCGEvent:cgEventMomentumUpdate]];
803 EXPECT_FALSE(called);
806 id touchMock = OCMClassMock([NSTouch
class]);
807 NSSet* touchSet = [NSSet setWithObject:touchMock];
808 id touchEventMock1 = OCMClassMock([NSEvent
class]);
809 OCMStub([touchEventMock1 allTouches]).andReturn(touchSet);
810 CGPoint touchLocation = {0, 0};
811 OCMStub([touchEventMock1 locationInWindow]).andReturn(touchLocation);
812 OCMStub([(NSEvent*)touchEventMock1 timestamp]).andReturn(0.150);
816 [viewController touchesBeganWithEvent:touchEventMock1];
817 EXPECT_FALSE(called);
820 id touchEventMock2 = OCMClassMock([NSEvent
class]);
821 OCMStub([touchEventMock2 allTouches]).andReturn(touchSet);
822 OCMStub([touchEventMock2 locationInWindow]).andReturn(touchLocation);
823 OCMStub([(NSEvent*)touchEventMock2 timestamp]).andReturn(0.005);
827 [viewController touchesBeganWithEvent:touchEventMock2];
829 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindScrollInertiaCancel);
830 EXPECT_EQ(last_event.device_kind, kFlutterPointerDeviceKindTrackpad);
833 CGEventRef cgEventMomentumEnd = CGEventCreateCopy(cgEventStart);
834 CGEventSetIntegerValueField(cgEventMomentumEnd, kCGScrollWheelEventScrollPhase, 0);
835 CGEventSetIntegerValueField(cgEventMomentumEnd, kCGScrollWheelEventMomentumPhase,
836 kCGMomentumScrollPhaseEnd);
839 [viewController scrollWheel:[NSEvent eventWithCGEvent:cgEventMomentumEnd]];
840 EXPECT_FALSE(called);
843 CGEventRef cgEventMayBegin = CGEventCreateCopy(cgEventStart);
844 CGEventSetIntegerValueField(cgEventMayBegin, kCGScrollWheelEventScrollPhase,
845 kCGScrollPhaseMayBegin);
848 [viewController scrollWheel:[NSEvent eventWithCGEvent:cgEventMayBegin]];
850 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
851 EXPECT_EQ(last_event.phase, kPanZoomStart);
852 EXPECT_EQ(last_event.device_kind, kFlutterPointerDeviceKindTrackpad);
853 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
856 CGEventRef cgEventCancel = CGEventCreateCopy(cgEventStart);
857 CGEventSetIntegerValueField(cgEventCancel, kCGScrollWheelEventScrollPhase,
858 kCGScrollPhaseCancelled);
861 [viewController scrollWheel:[NSEvent eventWithCGEvent:cgEventCancel]];
863 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
864 EXPECT_EQ(last_event.phase, kPanZoomEnd);
865 EXPECT_EQ(last_event.device_kind, kFlutterPointerDeviceKindTrackpad);
866 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
869 CGEventRef cgEventDiscrete = CGEventCreateScrollWheelEvent(NULL, kCGScrollEventUnitPixel, 1, 0);
870 CGEventSetType(cgEventDiscrete, kCGEventScrollWheel);
871 CGEventSetIntegerValueField(cgEventDiscrete, kCGScrollWheelEventIsContinuous, 0);
872 CGEventSetIntegerValueField(cgEventDiscrete, kCGScrollWheelEventDeltaAxis2, 1);
873 CGEventSetIntegerValueField(cgEventDiscrete, kCGScrollWheelEventDeltaAxis1, 2);
876 [viewController scrollWheel:[NSEvent eventWithCGEvent:cgEventDiscrete]];
878 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindScroll);
880 EXPECT_EQ(last_event.scroll_delta_x, -40 * viewController.flutterView.layer.contentsScale);
881 EXPECT_EQ(last_event.scroll_delta_y, -80 * viewController.flutterView.layer.contentsScale);
885 CGEventRef cgEventDiscreteShift =
886 CGEventCreateScrollWheelEvent(NULL, kCGScrollEventUnitPixel, 1, 0);
887 CGEventSetType(cgEventDiscreteShift, kCGEventScrollWheel);
889 CGEventSetIntegerValueField(cgEventDiscreteShift, kCGScrollWheelEventIsContinuous, 0);
890 CGEventSetIntegerValueField(cgEventDiscreteShift, kCGScrollWheelEventDeltaAxis2,
892 CGEventSetIntegerValueField(cgEventDiscreteShift, kCGScrollWheelEventDeltaAxis1,
896 [viewController scrollWheel:[NSEvent eventWithCGEvent:cgEventDiscreteShift]];
898 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindScroll);
900 EXPECT_FLOAT_EQ(last_event.scroll_delta_x, 0.0 * viewController.flutterView.layer.contentsScale);
901 EXPECT_FLOAT_EQ(last_event.scroll_delta_y,
902 -80.0 * viewController.flutterView.layer.contentsScale);
907 [viewController magnifyWithEvent:flutter::testing::MockGestureEvent(NSEventTypeMagnify,
908 NSEventPhaseBegan, 1, 0)];
910 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
911 EXPECT_EQ(last_event.phase, kPanZoomStart);
912 EXPECT_EQ(last_event.device_kind, kFlutterPointerDeviceKindTrackpad);
913 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
917 [viewController magnifyWithEvent:flutter::testing::MockGestureEvent(NSEventTypeMagnify,
918 NSEventPhaseChanged, 1, 0)];
920 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
921 EXPECT_EQ(last_event.phase, kPanZoomUpdate);
922 EXPECT_EQ(last_event.device_kind, kFlutterPointerDeviceKindTrackpad);
923 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
924 EXPECT_EQ(last_event.pan_x, 0);
925 EXPECT_EQ(last_event.pan_y, 0);
926 EXPECT_EQ(last_event.scale, 2);
928 EXPECT_EQ(last_event.rotation, 0);
932 [viewController magnifyWithEvent:flutter::testing::MockGestureEvent(NSEventTypeMagnify,
933 NSEventPhaseChanged, 1, 0)];
935 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
936 EXPECT_EQ(last_event.phase, kPanZoomUpdate);
937 EXPECT_EQ(last_event.device_kind, kFlutterPointerDeviceKindTrackpad);
938 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
939 EXPECT_EQ(last_event.pan_x, 0);
940 EXPECT_EQ(last_event.pan_y, 0);
941 EXPECT_EQ(last_event.scale, 4);
943 EXPECT_EQ(last_event.rotation, 0);
947 [viewController magnifyWithEvent:flutter::testing::MockGestureEvent(NSEventTypeMagnify,
948 NSEventPhaseEnded, 0, 0)];
950 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
951 EXPECT_EQ(last_event.phase, kPanZoomEnd);
952 EXPECT_EQ(last_event.device_kind, kFlutterPointerDeviceKindTrackpad);
953 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
958 [viewController rotateWithEvent:flutter::testing::MockGestureEvent(NSEventTypeRotate,
959 NSEventPhaseBegan, 1, 0)];
961 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
962 EXPECT_EQ(last_event.phase, kPanZoomStart);
963 EXPECT_EQ(last_event.device_kind, kFlutterPointerDeviceKindTrackpad);
964 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
968 [viewController rotateWithEvent:flutter::testing::MockGestureEvent(
969 NSEventTypeRotate, NSEventPhaseChanged, 0, -180)];
971 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
972 EXPECT_EQ(last_event.phase, kPanZoomUpdate);
973 EXPECT_EQ(last_event.device_kind, kFlutterPointerDeviceKindTrackpad);
974 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
975 EXPECT_EQ(last_event.pan_x, 0);
976 EXPECT_EQ(last_event.pan_y, 0);
977 EXPECT_EQ(last_event.scale, 1);
978 EXPECT_EQ(last_event.rotation, M_PI);
982 [viewController rotateWithEvent:flutter::testing::MockGestureEvent(
983 NSEventTypeRotate, NSEventPhaseChanged, 0, -360)];
985 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
986 EXPECT_EQ(last_event.phase, kPanZoomUpdate);
987 EXPECT_EQ(last_event.device_kind, kFlutterPointerDeviceKindTrackpad);
988 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
989 EXPECT_EQ(last_event.pan_x, 0);
990 EXPECT_EQ(last_event.pan_y, 0);
991 EXPECT_EQ(last_event.scale, 1);
992 EXPECT_EQ(last_event.rotation, 3 * M_PI);
996 [viewController rotateWithEvent:flutter::testing::MockGestureEvent(NSEventTypeRotate,
997 NSEventPhaseEnded, 0, 0)];
999 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
1000 EXPECT_EQ(last_event.phase, kPanZoomEnd);
1001 EXPECT_EQ(last_event.device_kind, kFlutterPointerDeviceKindTrackpad);
1002 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
1006 [viewController rotateWithEvent:flutter::testing::MockGestureEvent(NSEventTypeRotate,
1007 NSEventPhaseCancelled, 0, 0)];
1008 EXPECT_FALSE(called);
1014 - (bool)mouseAndGestureEventsAreHandledSeparately:(
id)engineMock {
1018 [viewController loadView];
1022 CGEventRef cgEventStart = CGEventCreateScrollWheelEvent(NULL, kCGScrollEventUnitPixel, 1, 0);
1023 CGEventSetType(cgEventStart, kCGEventScrollWheel);
1024 CGEventSetIntegerValueField(cgEventStart, kCGScrollWheelEventScrollPhase, kCGScrollPhaseBegan);
1025 CGEventSetIntegerValueField(cgEventStart, kCGScrollWheelEventIsContinuous, 1);
1026 [viewController scrollWheel:[NSEvent eventWithCGEvent:cgEventStart]];
1027 CFRelease(cgEventStart);
1029 CGEventRef cgEventUpdate = CGEventCreateCopy(cgEventStart);
1030 CGEventSetIntegerValueField(cgEventUpdate, kCGScrollWheelEventScrollPhase, kCGScrollPhaseChanged);
1031 CGEventSetIntegerValueField(cgEventUpdate, kCGScrollWheelEventDeltaAxis2, 1);
1032 CGEventSetIntegerValueField(cgEventUpdate, kCGScrollWheelEventDeltaAxis1, 2);
1033 [viewController scrollWheel:[NSEvent eventWithCGEvent:cgEventUpdate]];
1034 CFRelease(cgEventUpdate);
1036 NSEvent* mouseEvent = flutter::testing::CreateMouseEvent(0x00);
1037 [viewController mouseEntered:mouseEvent];
1038 [viewController mouseExited:mouseEvent];
1041 CGEventRef cgEventEnd = CGEventCreateCopy(cgEventStart);
1042 CGEventSetIntegerValueField(cgEventEnd, kCGScrollWheelEventScrollPhase, kCGScrollPhaseEnded);
1043 [viewController scrollWheel:[NSEvent eventWithCGEvent:cgEventEnd]];
1044 CFRelease(cgEventEnd);
1049 - (bool)testViewWillAppearCalledMultipleTimes:(
id)engineMock {
1053 [viewController viewWillAppear];
1054 [viewController viewWillAppear];
1062 [key isEqualToString:
@"Contents/Frameworks/App.framework/Resources/flutter_assets/test.png"]);
1070 EXPECT_TRUE([packageKey
1072 @"Contents/Frameworks/App.framework/Resources/flutter_assets/packages/test/test.png"]);
1076 static void SwizzledNoop(
id self,
SEL _cmd) {}
1088 - (bool)testMouseDownUpEventsSentToNextResponder:(
id)engineMock {
1089 if (@available(macOS 13.3.1, *)) {
1099 Method mouseDown = class_getInstanceMethod([NSResponder
class],
@selector(mouseDown:));
1100 Method mouseUp = class_getInstanceMethod([NSResponder
class],
@selector(mouseUp:));
1101 IMP noopImp = (IMP)SwizzledNoop;
1102 IMP origMouseDown = method_setImplementation(mouseDown, noopImp);
1103 IMP origMouseUp = method_setImplementation(mouseUp, noopImp);
1113 NSEvent* mouseEvent = flutter::testing::CreateMouseEvent(0x00);
1114 [view mouseDown:mouseEvent];
1119 [view mouseUp:mouseEvent];
1124 method_setImplementation(mouseDown, origMouseDown);
1125 method_setImplementation(mouseUp, origMouseUp);
1130 - (bool)testModifierKeysAreSynthesizedOnMouseMove:(
id)engineMock {
1133 [engineMock binaryMessenger])
1134 .andReturn(binaryMessengerMock);
1138 OCMStub([engineMock renderer]).andReturn(renderer_);
1141 __block NSMutableArray<KeyEventWrapper*>* events = [NSMutableArray array];
1142 OCMStub([[engineMock ignoringNonObjectArgs] sendKeyEvent:FlutterKeyEvent {}
1145 .andDo((^(NSInvocation* invocation) {
1146 FlutterKeyEvent* event;
1147 [invocation getArgument:&event atIndex:2];
1151 __block NSMutableArray<NSDictionary*>* channelEvents = [NSMutableArray array];
1152 OCMStub([binaryMessengerMock sendOnChannel:
@"flutter/keyevent"
1153 message:[OCMArg any]
1154 binaryReply:[OCMArg any]])
1155 .andDo((^(NSInvocation* invocation) {
1157 [invocation getArgument:&data atIndex:3];
1159 [channelEvents addObject:event];
1165 [viewController loadView];
1166 [viewController viewWillAppear];
1169 NSEvent* mouseEvent = flutter::testing::CreateMouseEvent(0x00);
1170 [viewController mouseMoved:mouseEvent];
1171 EXPECT_EQ([events count], 0u);
1175 FlutterKeyEvent* event;
1176 NSDictionary* channelEvent;
1177 NSNumber* logicalKey;
1178 NSNumber* physicalKey;
1179 NSEventModifierFlags flag = [flutter::keyCodeToModifierFlag[keyCode] unsignedLongValue];
1183 flag |= NSEventModifierFlagShift;
1186 flag |= NSEventModifierFlagControl;
1189 flag |= NSEventModifierFlagOption;
1192 flag |= NSEventModifierFlagCommand;
1196 NSEvent* mouseEvent = flutter::testing::CreateMouseEvent(flag);
1197 [viewController mouseMoved:mouseEvent];
1198 EXPECT_EQ([events count], 1u);
1199 event = events[0].data;
1200 logicalKey = [flutter::keyCodeToLogicalKey objectForKey:keyCode];
1201 physicalKey = [flutter::keyCodeToPhysicalKey objectForKey:keyCode];
1202 EXPECT_EQ(event->type, kFlutterKeyEventTypeDown);
1203 EXPECT_EQ(event->logical, logicalKey.unsignedLongLongValue);
1204 EXPECT_EQ(event->physical, physicalKey.unsignedLongLongValue);
1205 EXPECT_EQ(event->synthesized,
true);
1207 channelEvent = channelEvents[0];
1208 EXPECT_TRUE([channelEvent[
@"type"] isEqual:
@"keydown"]);
1209 EXPECT_TRUE([channelEvent[
@"keyCode"] isEqual:keyCode]);
1210 EXPECT_TRUE([channelEvent[
@"modifiers"] isEqual:@(flag)]);
1213 mouseEvent = flutter::testing::CreateMouseEvent(0x00);
1214 [viewController mouseMoved:mouseEvent];
1215 EXPECT_EQ([events count], 2u);
1216 event = events[1].data;
1217 logicalKey = [flutter::keyCodeToLogicalKey objectForKey:keyCode];
1218 physicalKey = [flutter::keyCodeToPhysicalKey objectForKey:keyCode];
1219 EXPECT_EQ(event->type, kFlutterKeyEventTypeUp);
1220 EXPECT_EQ(event->logical, logicalKey.unsignedLongLongValue);
1221 EXPECT_EQ(event->physical, physicalKey.unsignedLongLongValue);
1222 EXPECT_EQ(event->synthesized,
true);
1224 channelEvent = channelEvents[1];
1225 EXPECT_TRUE([channelEvent[
@"type"] isEqual:
@"keyup"]);
1226 EXPECT_TRUE([channelEvent[
@"keyCode"] isEqual:keyCode]);
1227 EXPECT_TRUE([channelEvent[
@"modifiers"] isEqual:@(0)]);
1229 [events removeAllObjects];
1230 [channelEvents removeAllObjects];
1242 OCMStub([engineMock renderer]).andReturn(renderer_);
1247 [viewController loadView];
1248 weakController = viewController;
1250 [engineMock shutDownEngine];
1253 EXPECT_EQ(weakController, nil);