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)testMouseDownUpEventsSentToNextResponder:(
id)mockEngine;
105 - (bool)testModifierKeysAreSynthesizedOnMouseMove:(
id)mockEngine;
106 - (bool)testViewWillAppearCalledMultipleTimes:(
id)mockEngine;
107 - (bool)testFlutterViewIsConfigured:(
id)mockEngine;
112 + (void)respondFalseForSendEvent:(const FlutterKeyEvent&)event
113 callback:(nullable FlutterKeyEventCallback)callback
114 userData:(nullable
void*)userData;
117 #pragma mark - Static helper functions
119 using namespace ::flutter::testing::keycodes;
125 id MockGestureEvent(NSEventType type, NSEventPhase phase,
double magnification,
double rotation) {
126 id event = [OCMockObject mockForClass:[NSEvent class]];
127 NSPoint locationInWindow = NSMakePoint(0, 0);
130 NSTimeInterval timestamp = 1;
131 NSUInteger modifierFlags = 0;
132 [(NSEvent*)[[event stub] andReturnValue:OCMOCK_VALUE(type)] type];
133 [(NSEvent*)[[event stub] andReturnValue:OCMOCK_VALUE(phase)] phase];
134 [(NSEvent*)[[event stub] andReturnValue:OCMOCK_VALUE(locationInWindow)] locationInWindow];
135 [(NSEvent*)[[event stub] andReturnValue:OCMOCK_VALUE(deltaX)] deltaX];
136 [(NSEvent*)[[event stub] andReturnValue:OCMOCK_VALUE(deltaY)] deltaY];
137 [(NSEvent*)[[event stub] andReturnValue:OCMOCK_VALUE(timestamp)] timestamp];
138 [(NSEvent*)[[event stub] andReturnValue:OCMOCK_VALUE(modifierFlags)] modifierFlags];
139 [(NSEvent*)[[event stub] andReturnValue:OCMOCK_VALUE(magnification)] magnification];
140 [(NSEvent*)[[event stub] andReturnValue:OCMOCK_VALUE(rotation)] rotation];
146 NSString* fixtures = @(testing::GetFixturesPath());
148 initWithAssetsPath:fixtures
149 ICUDataPath:[fixtures stringByAppendingString:@"/icudtl.dat"]];
150 return [[
FlutterEngine alloc] initWithName:@"test" project:project allowHeadlessExecution:true];
153 NSResponder* mockResponder() {
154 NSResponder* mock = OCMStrictClassMock([NSResponder
class]);
155 OCMStub([mock keyDown:[OCMArg any]]).andDo(nil);
156 OCMStub([mock keyUp:[OCMArg any]]).andDo(nil);
157 OCMStub([mock flagsChanged:[OCMArg any]]).andDo(nil);
161 NSEvent* CreateMouseEvent(NSEventModifierFlags modifierFlags) {
162 return [NSEvent mouseEventWithType:NSEventTypeMouseMoved
164 modifierFlags:modifierFlags
175 #pragma mark - gtest tests
184 [viewControllerMock loadView];
185 auto subViews = [viewControllerMock.view subviews];
187 EXPECT_EQ([subViews count], 1u);
188 EXPECT_EQ(subViews[0], viewControllerMock.flutterView);
190 NSTextField* textField = [[NSTextField alloc] initWithFrame:NSMakeRect(0, 0, 1, 1)];
191 [viewControllerMock.view addSubview:textField];
193 subViews = [viewControllerMock.view subviews];
194 EXPECT_EQ([subViews count], 2u);
196 auto accessibilityChildren = viewControllerMock.view.accessibilityChildren;
198 EXPECT_EQ([accessibilityChildren count], 1u);
199 EXPECT_EQ(accessibilityChildren[0], viewControllerMock.flutterView);
204 [viewControllerMock loadView];
205 EXPECT_EQ([viewControllerMock.flutterView acceptsFirstMouse:nil], YES);
213 [viewController loadView];
214 [engine setViewController:viewController];
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();
327 #pragma mark - FlutterViewControllerTestObjC
331 - (bool)testKeyEventsAreSentToFramework:(
id)engineMock {
334 [engineMock binaryMessenger])
335 .andReturn(binaryMessengerMock);
336 OCMStub([[engineMock ignoringNonObjectArgs] sendKeyEvent:FlutterKeyEvent {}
340 @selector(respondFalseForSendEvent:callback:userData:));
344 NSDictionary* expectedEvent = @{
345 @"keymap" :
@"macos",
346 @"type" :
@"keydown",
348 @"modifiers" : @(538968064),
349 @"characters" :
@".",
350 @"charactersIgnoringModifiers" :
@".",
353 CGEventRef cgEvent = CGEventCreateKeyboardEvent(NULL, 65, TRUE);
354 NSEvent*
event = [NSEvent eventWithCGEvent:cgEvent];
355 [viewController viewWillAppear];
356 [viewController keyDown:event];
359 [binaryMessengerMock sendOnChannel:
@"flutter/keyevent"
360 message:encodedKeyEvent
361 binaryReply:[OCMArg any]]);
369 - (bool)testCtrlTabKeyEventIsPropagated:(
id)engineMock {
370 __block
bool called =
false;
371 __block FlutterKeyEvent last_event;
372 OCMStub([[engineMock ignoringNonObjectArgs] sendKeyEvent:FlutterKeyEvent {}
375 .andDo((^(NSInvocation* invocation) {
376 FlutterKeyEvent* event;
377 [invocation getArgument:&event atIndex:2];
385 NSEvent*
event = [NSEvent keyEventWithType:NSEventTypeKeyDown
387 modifierFlags:0x40101
392 charactersIgnoringModifiers:@""
395 const uint64_t kPhysicalKeyTab = 0x7002b;
397 [viewController viewWillAppear];
399 NSWindow* window = [[NSWindow alloc] initWithContentRect:NSMakeRect(0, 0, 800, 600)
400 styleMask:NSBorderlessWindowMask
401 backing:NSBackingStoreBuffered
403 window.contentView = viewController.view;
404 [window makeFirstResponder:viewController.flutterView];
405 [viewController.view performKeyEquivalent:event];
408 EXPECT_EQ(last_event.type, kFlutterKeyEventTypeDown);
409 EXPECT_EQ(last_event.physical, kPhysicalKeyTab);
413 - (bool)testKeyEquivalentIsPassedToTextInputPlugin:(
id)engineMock {
414 __block
bool called =
false;
415 __block FlutterKeyEvent last_event;
416 OCMStub([[engineMock ignoringNonObjectArgs] sendKeyEvent:FlutterKeyEvent {}
419 .andDo((^(NSInvocation* invocation) {
420 FlutterKeyEvent* event;
421 [invocation getArgument:&event atIndex:2];
429 NSEvent*
event = [NSEvent keyEventWithType:NSEventTypeKeyDown
431 modifierFlags:0x40101
436 charactersIgnoringModifiers:@""
439 const uint64_t kPhysicalKeyTab = 0x7002b;
441 [viewController viewWillAppear];
443 NSWindow* window = [[NSWindow alloc] initWithContentRect:NSMakeRect(0, 0, 800, 600)
444 styleMask:NSBorderlessWindowMask
445 backing:NSBackingStoreBuffered
447 window.contentView = viewController.view;
449 [viewController.view addSubview:viewController.textInputPlugin];
453 [window makeFirstResponder:viewController.textInputPlugin];
455 [viewController.view performKeyEquivalent:event];
458 EXPECT_EQ(last_event.type, kFlutterKeyEventTypeDown);
459 EXPECT_EQ(last_event.physical, kPhysicalKeyTab);
463 - (bool)testKeyEventsArePropagatedIfNotHandled:(
id)engineMock {
466 [engineMock binaryMessenger])
467 .andReturn(binaryMessengerMock);
468 OCMStub([[engineMock ignoringNonObjectArgs] sendKeyEvent:FlutterKeyEvent {}
472 @selector(respondFalseForSendEvent:callback:userData:));
476 id responderMock = flutter::testing::mockResponder();
478 viewController.nextResponder = responderWrapper;
479 NSDictionary* expectedEvent = @{
480 @"keymap" :
@"macos",
481 @"type" :
@"keydown",
483 @"modifiers" : @(538968064),
484 @"characters" :
@".",
485 @"charactersIgnoringModifiers" :
@".",
488 CGEventRef cgEvent = CGEventCreateKeyboardEvent(NULL, 65, TRUE);
489 NSEvent*
event = [NSEvent eventWithCGEvent:cgEvent];
491 [binaryMessengerMock sendOnChannel:
@"flutter/keyevent"
492 message:encodedKeyEvent
493 binaryReply:[OCMArg any]])
494 .andDo((^(NSInvocation* invocation) {
496 [invocation getArgument:&handler atIndex:4];
497 NSDictionary* reply = @{
498 @"handled" : @(
false),
501 handler(encodedReply);
503 [viewController viewWillAppear];
504 [viewController keyDown:event];
507 [responderMock keyDown:[OCMArg any]]);
509 [binaryMessengerMock sendOnChannel:
@"flutter/keyevent"
510 message:encodedKeyEvent
511 binaryReply:[OCMArg any]]);
518 - (bool)testFlutterViewIsConfigured:(
id)engineMock {
520 OCMStub([engineMock renderer]).andReturn(renderer_);
525 [viewController loadView];
529 OCMVerify([engineMock renderer]);
537 - (bool)testFlagsChangedEventsArePropagatedIfNotHandled:(
id)engineMock {
540 [engineMock binaryMessenger])
541 .andReturn(binaryMessengerMock);
542 OCMStub([[engineMock ignoringNonObjectArgs] sendKeyEvent:FlutterKeyEvent {}
546 @selector(respondFalseForSendEvent:callback:userData:));
550 id responderMock = flutter::testing::mockResponder();
552 viewController.nextResponder = responderWrapper;
553 NSDictionary* expectedEvent = @{
554 @"keymap" :
@"macos",
555 @"type" :
@"keydown",
557 @"modifiers" : @(537001986),
560 CGEventRef cgEvent = CGEventCreateKeyboardEvent(NULL, 56, TRUE);
561 CGEventSetType(cgEvent, kCGEventFlagsChanged);
562 NSEvent*
event = [NSEvent eventWithCGEvent:cgEvent];
564 [binaryMessengerMock sendOnChannel:
@"flutter/keyevent"
565 message:encodedKeyEvent
566 binaryReply:[OCMArg any]])
567 .andDo((^(NSInvocation* invocation) {
569 [invocation getArgument:&handler atIndex:4];
570 NSDictionary* reply = @{
571 @"handled" : @(
false),
574 handler(encodedReply);
576 [viewController viewWillAppear];
577 [viewController flagsChanged:event];
580 [binaryMessengerMock sendOnChannel:
@"flutter/keyevent"
581 message:encodedKeyEvent
582 binaryReply:[OCMArg any]]);
583 }
@catch (NSException* e) {
584 NSLog(
@"%@", e.reason);
590 - (bool)testKeyEventsAreNotPropagatedIfHandled:(
id)engineMock {
593 [engineMock binaryMessenger])
594 .andReturn(binaryMessengerMock);
595 OCMStub([[engineMock ignoringNonObjectArgs] sendKeyEvent:FlutterKeyEvent {}
599 @selector(respondFalseForSendEvent:callback:userData:));
603 id responderMock = flutter::testing::mockResponder();
605 viewController.nextResponder = responderWrapper;
606 NSDictionary* expectedEvent = @{
607 @"keymap" :
@"macos",
608 @"type" :
@"keydown",
610 @"modifiers" : @(538968064),
611 @"characters" :
@".",
612 @"charactersIgnoringModifiers" :
@".",
615 CGEventRef cgEvent = CGEventCreateKeyboardEvent(NULL, 65, TRUE);
616 NSEvent*
event = [NSEvent eventWithCGEvent:cgEvent];
618 [binaryMessengerMock sendOnChannel:
@"flutter/keyevent"
619 message:encodedKeyEvent
620 binaryReply:[OCMArg any]])
621 .andDo((^(NSInvocation* invocation) {
623 [invocation getArgument:&handler atIndex:4];
624 NSDictionary* reply = @{
625 @"handled" : @(
true),
628 handler(encodedReply);
630 [viewController viewWillAppear];
631 [viewController keyDown:event];
634 never(), [responderMock keyDown:[OCMArg any]]);
636 [binaryMessengerMock sendOnChannel:
@"flutter/keyevent"
637 message:encodedKeyEvent
638 binaryReply:[OCMArg any]]);
645 - (bool)testKeyboardIsRestartedOnEngineRestart:(
id)engineMock {
648 [engineMock binaryMessenger])
649 .andReturn(binaryMessengerMock);
650 __block
bool called =
false;
651 __block FlutterKeyEvent last_event;
652 OCMStub([[engineMock ignoringNonObjectArgs] sendKeyEvent:FlutterKeyEvent {}
655 .andDo((^(NSInvocation* invocation) {
656 FlutterKeyEvent* event;
657 [invocation getArgument:&event atIndex:2];
665 [viewController viewWillAppear];
666 NSEvent* keyADown = [NSEvent keyEventWithType:NSEventTypeKeyDown
673 charactersIgnoringModifiers:@"a"
676 const uint64_t kPhysicalKeyA = 0x70004;
683 [viewController keyDown:keyADown];
685 EXPECT_EQ(last_event.type, kFlutterKeyEventTypeDown);
686 EXPECT_EQ(last_event.physical, kPhysicalKeyA);
691 [viewController keyDown:keyADown];
693 EXPECT_EQ(last_event.type, kFlutterKeyEventTypeDown);
694 EXPECT_EQ(last_event.physical, kPhysicalKeyA);
698 + (void)respondFalseForSendEvent:(const FlutterKeyEvent&)event
699 callback:(nullable FlutterKeyEventCallback)callback
700 userData:(nullable
void*)userData {
701 if (callback !=
nullptr) {
702 callback(
false, userData);
706 - (bool)testTrackpadGesturesAreSentToFramework:(
id)engineMock {
709 OCMStub([engineMock renderer]).andReturn(renderer_);
710 __block
bool called =
false;
711 __block FlutterPointerEvent last_event;
712 OCMStub([[engineMock ignoringNonObjectArgs] sendPointerEvent:FlutterPointerEvent{}])
713 .andDo((^(NSInvocation* invocation) {
714 FlutterPointerEvent* event;
715 [invocation getArgument:&event atIndex:2];
723 [viewController loadView];
727 CGEventRef cgEventStart = CGEventCreateScrollWheelEvent(NULL, kCGScrollEventUnitPixel, 1, 0);
728 CGEventSetType(cgEventStart, kCGEventScrollWheel);
729 CGEventSetIntegerValueField(cgEventStart, kCGScrollWheelEventScrollPhase, kCGScrollPhaseBegan);
730 CGEventSetIntegerValueField(cgEventStart, kCGScrollWheelEventIsContinuous, 1);
733 [viewController scrollWheel:[NSEvent eventWithCGEvent:cgEventStart]];
735 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
736 EXPECT_EQ(last_event.phase, kPanZoomStart);
737 EXPECT_EQ(last_event.device_kind, kFlutterPointerDeviceKindTrackpad);
738 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
741 CGEventRef cgEventUpdate = CGEventCreateCopy(cgEventStart);
742 CGEventSetIntegerValueField(cgEventUpdate, kCGScrollWheelEventScrollPhase, kCGScrollPhaseChanged);
743 CGEventSetIntegerValueField(cgEventUpdate, kCGScrollWheelEventDeltaAxis2, 1);
744 CGEventSetIntegerValueField(cgEventUpdate, kCGScrollWheelEventDeltaAxis1, 2);
747 [viewController scrollWheel:[NSEvent eventWithCGEvent:cgEventUpdate]];
749 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
750 EXPECT_EQ(last_event.phase, kPanZoomUpdate);
751 EXPECT_EQ(last_event.device_kind, kFlutterPointerDeviceKindTrackpad);
752 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
753 EXPECT_EQ(last_event.pan_x, 8 * viewController.flutterView.layer.contentsScale);
754 EXPECT_EQ(last_event.pan_y, 16 * viewController.flutterView.layer.contentsScale);
758 [viewController scrollWheel:[NSEvent eventWithCGEvent:cgEventUpdate]];
760 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
761 EXPECT_EQ(last_event.phase, kPanZoomUpdate);
762 EXPECT_EQ(last_event.device_kind, kFlutterPointerDeviceKindTrackpad);
763 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
764 EXPECT_EQ(last_event.pan_x, 16 * viewController.flutterView.layer.contentsScale);
765 EXPECT_EQ(last_event.pan_y, 32 * viewController.flutterView.layer.contentsScale);
768 CGEventRef cgEventEnd = CGEventCreateCopy(cgEventStart);
769 CGEventSetIntegerValueField(cgEventEnd, kCGScrollWheelEventScrollPhase, kCGScrollPhaseEnded);
772 [viewController scrollWheel:[NSEvent eventWithCGEvent:cgEventEnd]];
774 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
775 EXPECT_EQ(last_event.phase, kPanZoomEnd);
776 EXPECT_EQ(last_event.device_kind, kFlutterPointerDeviceKindTrackpad);
777 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
780 CGEventRef cgEventMomentumStart = CGEventCreateCopy(cgEventStart);
781 CGEventSetIntegerValueField(cgEventMomentumStart, kCGScrollWheelEventScrollPhase, 0);
782 CGEventSetIntegerValueField(cgEventMomentumStart, kCGScrollWheelEventMomentumPhase,
783 kCGMomentumScrollPhaseBegin);
786 [viewController scrollWheel:[NSEvent eventWithCGEvent:cgEventMomentumStart]];
787 EXPECT_FALSE(called);
790 CGEventRef cgEventMomentumUpdate = CGEventCreateCopy(cgEventStart);
791 CGEventSetIntegerValueField(cgEventMomentumUpdate, kCGScrollWheelEventScrollPhase, 0);
792 CGEventSetIntegerValueField(cgEventMomentumUpdate, kCGScrollWheelEventMomentumPhase,
793 kCGMomentumScrollPhaseContinue);
796 [viewController scrollWheel:[NSEvent eventWithCGEvent:cgEventMomentumUpdate]];
797 EXPECT_FALSE(called);
800 id touchMock = OCMClassMock([NSTouch
class]);
801 NSSet* touchSet = [NSSet setWithObject:touchMock];
802 id touchEventMock1 = OCMClassMock([NSEvent
class]);
803 OCMStub([touchEventMock1 allTouches]).andReturn(touchSet);
804 CGPoint touchLocation = {0, 0};
805 OCMStub([touchEventMock1 locationInWindow]).andReturn(touchLocation);
806 OCMStub([(NSEvent*)touchEventMock1 timestamp]).andReturn(0.150);
810 [viewController touchesBeganWithEvent:touchEventMock1];
811 EXPECT_FALSE(called);
814 id touchEventMock2 = OCMClassMock([NSEvent
class]);
815 OCMStub([touchEventMock2 allTouches]).andReturn(touchSet);
816 OCMStub([touchEventMock2 locationInWindow]).andReturn(touchLocation);
817 OCMStub([(NSEvent*)touchEventMock2 timestamp]).andReturn(0.005);
821 [viewController touchesBeganWithEvent:touchEventMock2];
823 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindScrollInertiaCancel);
824 EXPECT_EQ(last_event.device_kind, kFlutterPointerDeviceKindTrackpad);
827 CGEventRef cgEventMomentumEnd = CGEventCreateCopy(cgEventStart);
828 CGEventSetIntegerValueField(cgEventMomentumEnd, kCGScrollWheelEventScrollPhase, 0);
829 CGEventSetIntegerValueField(cgEventMomentumEnd, kCGScrollWheelEventMomentumPhase,
830 kCGMomentumScrollPhaseEnd);
833 [viewController scrollWheel:[NSEvent eventWithCGEvent:cgEventMomentumEnd]];
834 EXPECT_FALSE(called);
837 CGEventRef cgEventMayBegin = CGEventCreateCopy(cgEventStart);
838 CGEventSetIntegerValueField(cgEventMayBegin, kCGScrollWheelEventScrollPhase,
839 kCGScrollPhaseMayBegin);
842 [viewController scrollWheel:[NSEvent eventWithCGEvent:cgEventMayBegin]];
844 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
845 EXPECT_EQ(last_event.phase, kPanZoomStart);
846 EXPECT_EQ(last_event.device_kind, kFlutterPointerDeviceKindTrackpad);
847 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
850 CGEventRef cgEventCancel = CGEventCreateCopy(cgEventStart);
851 CGEventSetIntegerValueField(cgEventCancel, kCGScrollWheelEventScrollPhase,
852 kCGScrollPhaseCancelled);
855 [viewController scrollWheel:[NSEvent eventWithCGEvent:cgEventCancel]];
857 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
858 EXPECT_EQ(last_event.phase, kPanZoomEnd);
859 EXPECT_EQ(last_event.device_kind, kFlutterPointerDeviceKindTrackpad);
860 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
863 CGEventRef cgEventDiscrete = CGEventCreateScrollWheelEvent(NULL, kCGScrollEventUnitPixel, 1, 0);
864 CGEventSetType(cgEventDiscrete, kCGEventScrollWheel);
865 CGEventSetIntegerValueField(cgEventDiscrete, kCGScrollWheelEventIsContinuous, 0);
866 CGEventSetIntegerValueField(cgEventDiscrete, kCGScrollWheelEventDeltaAxis2, 1);
867 CGEventSetIntegerValueField(cgEventDiscrete, kCGScrollWheelEventDeltaAxis1, 2);
870 [viewController scrollWheel:[NSEvent eventWithCGEvent:cgEventDiscrete]];
872 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindScroll);
874 EXPECT_EQ(last_event.scroll_delta_x, -40 * viewController.flutterView.layer.contentsScale);
875 EXPECT_EQ(last_event.scroll_delta_y, -80 * viewController.flutterView.layer.contentsScale);
879 CGEventRef cgEventDiscreteShift =
880 CGEventCreateScrollWheelEvent(NULL, kCGScrollEventUnitPixel, 1, 0);
881 CGEventSetType(cgEventDiscreteShift, kCGEventScrollWheel);
883 CGEventSetIntegerValueField(cgEventDiscreteShift, kCGScrollWheelEventIsContinuous, 0);
884 CGEventSetIntegerValueField(cgEventDiscreteShift, kCGScrollWheelEventDeltaAxis2,
886 CGEventSetIntegerValueField(cgEventDiscreteShift, kCGScrollWheelEventDeltaAxis1,
890 [viewController scrollWheel:[NSEvent eventWithCGEvent:cgEventDiscreteShift]];
892 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindScroll);
894 EXPECT_FLOAT_EQ(last_event.scroll_delta_x, 0.0 * viewController.flutterView.layer.contentsScale);
895 EXPECT_FLOAT_EQ(last_event.scroll_delta_y,
896 -80.0 * viewController.flutterView.layer.contentsScale);
901 [viewController magnifyWithEvent:flutter::testing::MockGestureEvent(NSEventTypeMagnify,
902 NSEventPhaseBegan, 1, 0)];
904 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
905 EXPECT_EQ(last_event.phase, kPanZoomStart);
906 EXPECT_EQ(last_event.device_kind, kFlutterPointerDeviceKindTrackpad);
907 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
911 [viewController magnifyWithEvent:flutter::testing::MockGestureEvent(NSEventTypeMagnify,
912 NSEventPhaseChanged, 1, 0)];
914 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
915 EXPECT_EQ(last_event.phase, kPanZoomUpdate);
916 EXPECT_EQ(last_event.device_kind, kFlutterPointerDeviceKindTrackpad);
917 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
918 EXPECT_EQ(last_event.pan_x, 0);
919 EXPECT_EQ(last_event.pan_y, 0);
920 EXPECT_EQ(last_event.scale, 2);
922 EXPECT_EQ(last_event.rotation, 0);
926 [viewController magnifyWithEvent:flutter::testing::MockGestureEvent(NSEventTypeMagnify,
927 NSEventPhaseChanged, 1, 0)];
929 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
930 EXPECT_EQ(last_event.phase, kPanZoomUpdate);
931 EXPECT_EQ(last_event.device_kind, kFlutterPointerDeviceKindTrackpad);
932 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
933 EXPECT_EQ(last_event.pan_x, 0);
934 EXPECT_EQ(last_event.pan_y, 0);
935 EXPECT_EQ(last_event.scale, 4);
937 EXPECT_EQ(last_event.rotation, 0);
941 [viewController magnifyWithEvent:flutter::testing::MockGestureEvent(NSEventTypeMagnify,
942 NSEventPhaseEnded, 0, 0)];
944 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
945 EXPECT_EQ(last_event.phase, kPanZoomEnd);
946 EXPECT_EQ(last_event.device_kind, kFlutterPointerDeviceKindTrackpad);
947 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
952 [viewController rotateWithEvent:flutter::testing::MockGestureEvent(NSEventTypeRotate,
953 NSEventPhaseBegan, 1, 0)];
955 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
956 EXPECT_EQ(last_event.phase, kPanZoomStart);
957 EXPECT_EQ(last_event.device_kind, kFlutterPointerDeviceKindTrackpad);
958 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
962 [viewController rotateWithEvent:flutter::testing::MockGestureEvent(
963 NSEventTypeRotate, NSEventPhaseChanged, 0, -180)];
965 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
966 EXPECT_EQ(last_event.phase, kPanZoomUpdate);
967 EXPECT_EQ(last_event.device_kind, kFlutterPointerDeviceKindTrackpad);
968 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
969 EXPECT_EQ(last_event.pan_x, 0);
970 EXPECT_EQ(last_event.pan_y, 0);
971 EXPECT_EQ(last_event.scale, 1);
972 EXPECT_EQ(last_event.rotation, M_PI);
976 [viewController rotateWithEvent:flutter::testing::MockGestureEvent(
977 NSEventTypeRotate, NSEventPhaseChanged, 0, -360)];
979 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
980 EXPECT_EQ(last_event.phase, kPanZoomUpdate);
981 EXPECT_EQ(last_event.device_kind, kFlutterPointerDeviceKindTrackpad);
982 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
983 EXPECT_EQ(last_event.pan_x, 0);
984 EXPECT_EQ(last_event.pan_y, 0);
985 EXPECT_EQ(last_event.scale, 1);
986 EXPECT_EQ(last_event.rotation, 3 * M_PI);
990 [viewController rotateWithEvent:flutter::testing::MockGestureEvent(NSEventTypeRotate,
991 NSEventPhaseEnded, 0, 0)];
993 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
994 EXPECT_EQ(last_event.phase, kPanZoomEnd);
995 EXPECT_EQ(last_event.device_kind, kFlutterPointerDeviceKindTrackpad);
996 EXPECT_EQ(last_event.signal_kind, kFlutterPointerSignalKindNone);
1000 [viewController rotateWithEvent:flutter::testing::MockGestureEvent(NSEventTypeRotate,
1001 NSEventPhaseCancelled, 0, 0)];
1002 EXPECT_FALSE(called);
1007 - (bool)testViewWillAppearCalledMultipleTimes:(
id)engineMock {
1011 [viewController viewWillAppear];
1012 [viewController viewWillAppear];
1020 [key isEqualToString:
@"Contents/Frameworks/App.framework/Resources/flutter_assets/test.png"]);
1028 EXPECT_TRUE([packageKey
1030 @"Contents/Frameworks/App.framework/Resources/flutter_assets/packages/test/test.png"]);
1034 static void SwizzledNoop(
id self,
SEL _cmd) {}
1043 - (bool)testMouseDownUpEventsSentToNextResponder:(
id)engineMock {
1048 Method mouseDown = class_getInstanceMethod([NSResponder
class],
@selector(mouseDown:));
1049 Method mouseUp = class_getInstanceMethod([NSResponder
class],
@selector(mouseUp:));
1050 IMP noopImp = (IMP)SwizzledNoop;
1051 IMP origMouseDown = method_setImplementation(mouseDown, noopImp);
1052 IMP origMouseUp = method_setImplementation(mouseUp, noopImp);
1062 NSEvent* mouseEvent = flutter::testing::CreateMouseEvent(0x00);
1063 [view mouseDown:mouseEvent];
1068 [view mouseUp:mouseEvent];
1073 method_setImplementation(mouseDown, origMouseDown);
1074 method_setImplementation(mouseUp, origMouseUp);
1079 - (bool)testModifierKeysAreSynthesizedOnMouseMove:(
id)engineMock {
1082 [engineMock binaryMessenger])
1083 .andReturn(binaryMessengerMock);
1087 OCMStub([engineMock renderer]).andReturn(renderer_);
1090 __block NSMutableArray<KeyEventWrapper*>* events = [NSMutableArray array];
1091 OCMStub([[engineMock ignoringNonObjectArgs] sendKeyEvent:FlutterKeyEvent {}
1094 .andDo((^(NSInvocation* invocation) {
1095 FlutterKeyEvent* event;
1096 [invocation getArgument:&event atIndex:2];
1100 __block NSMutableArray<NSDictionary*>* channelEvents = [NSMutableArray array];
1101 OCMStub([binaryMessengerMock sendOnChannel:
@"flutter/keyevent"
1102 message:[OCMArg any]
1103 binaryReply:[OCMArg any]])
1104 .andDo((^(NSInvocation* invocation) {
1106 [invocation getArgument:&data atIndex:3];
1108 [channelEvents addObject:event];
1114 [viewController loadView];
1115 [viewController viewWillAppear];
1118 NSEvent* mouseEvent = flutter::testing::CreateMouseEvent(0x00);
1119 [viewController mouseMoved:mouseEvent];
1120 EXPECT_EQ([events count], 0u);
1124 FlutterKeyEvent* event;
1125 NSDictionary* channelEvent;
1126 NSNumber* logicalKey;
1127 NSNumber* physicalKey;
1128 NSEventModifierFlags flag = [flutter::keyCodeToModifierFlag[keyCode] unsignedLongValue];
1132 flag |= NSEventModifierFlagShift;
1135 flag |= NSEventModifierFlagControl;
1138 flag |= NSEventModifierFlagOption;
1141 flag |= NSEventModifierFlagCommand;
1145 NSEvent* mouseEvent = flutter::testing::CreateMouseEvent(flag);
1146 [viewController mouseMoved:mouseEvent];
1147 EXPECT_EQ([events count], 1u);
1148 event = events[0].data;
1149 logicalKey = [flutter::keyCodeToLogicalKey objectForKey:keyCode];
1150 physicalKey = [flutter::keyCodeToPhysicalKey objectForKey:keyCode];
1151 EXPECT_EQ(event->type, kFlutterKeyEventTypeDown);
1152 EXPECT_EQ(event->logical, logicalKey.unsignedLongLongValue);
1153 EXPECT_EQ(event->physical, physicalKey.unsignedLongLongValue);
1154 EXPECT_EQ(event->synthesized,
true);
1156 channelEvent = channelEvents[0];
1157 EXPECT_TRUE([channelEvent[
@"type"] isEqual:
@"keydown"]);
1158 EXPECT_TRUE([channelEvent[
@"keyCode"] isEqual:keyCode]);
1159 EXPECT_TRUE([channelEvent[
@"modifiers"] isEqual:@(flag)]);
1162 mouseEvent = flutter::testing::CreateMouseEvent(0x00);
1163 [viewController mouseMoved:mouseEvent];
1164 EXPECT_EQ([events count], 2u);
1165 event = events[1].data;
1166 logicalKey = [flutter::keyCodeToLogicalKey objectForKey:keyCode];
1167 physicalKey = [flutter::keyCodeToPhysicalKey objectForKey:keyCode];
1168 EXPECT_EQ(event->type, kFlutterKeyEventTypeUp);
1169 EXPECT_EQ(event->logical, logicalKey.unsignedLongLongValue);
1170 EXPECT_EQ(event->physical, physicalKey.unsignedLongLongValue);
1171 EXPECT_EQ(event->synthesized,
true);
1173 channelEvent = channelEvents[1];
1174 EXPECT_TRUE([channelEvent[
@"type"] isEqual:
@"keyup"]);
1175 EXPECT_TRUE([channelEvent[
@"keyCode"] isEqual:keyCode]);
1176 EXPECT_TRUE([channelEvent[
@"modifiers"] isEqual:@(0)]);
1178 [events removeAllObjects];
1179 [channelEvents removeAllObjects];
1191 OCMStub([engineMock renderer]).andReturn(renderer_);
1196 [viewController loadView];
1197 weakController = viewController;
1199 [engineMock shutDownEngine];
1202 EXPECT_EQ(weakController, nil);