22// Use of this source code is governed by a BSD-style license that can be
33// found in the LICENSE file.
44
5+ #import " KeyCodeMap_Internal.h"
56#import " flutter/shell/platform/darwin/macos/framework/Headers/FlutterViewController.h"
67#import " flutter/shell/platform/darwin/macos/framework/Source/FlutterViewController_Internal.h"
78
1314#import " flutter/shell/platform/darwin/macos/framework/Source/FlutterEngine_Internal.h"
1415#import " flutter/shell/platform/darwin/macos/framework/Source/FlutterRenderer.h"
1516#import " flutter/shell/platform/darwin/macos/framework/Source/FlutterViewControllerTestUtils.h"
17+ #include " flutter/shell/platform/embedder/test_utils/key_codes.g.h"
1618#import " flutter/testing/testing.h"
1719
20+ // A wrap to convert FlutterKeyEvent to a ObjC class.
21+ @interface KeyEventWrapper : NSObject
22+ @property (nonatomic ) FlutterKeyEvent* data;
23+ - (nonnull instancetype )initWithEvent : (const FlutterKeyEvent*)event ;
24+ @end
25+
26+ @implementation KeyEventWrapper
27+ - (instancetype )initWithEvent : (const FlutterKeyEvent*)event {
28+ self = [super init ];
29+ _data = new FlutterKeyEvent (*event);
30+ return self;
31+ }
32+
33+ - (void )dealloc {
34+ delete _data;
35+ }
36+ @end
37+
1838@interface FlutterViewControllerTestObjC : NSObject
1939- (bool )testKeyEventsAreSentToFramework ;
2040- (bool )testKeyEventsArePropagatedIfNotHandled ;
2141- (bool )testKeyEventsAreNotPropagatedIfHandled ;
2242- (bool )testFlagsChangedEventsArePropagatedIfNotHandled ;
2343- (bool )testKeyboardIsRestartedOnEngineRestart ;
2444- (bool )testTrackpadGesturesAreSentToFramework ;
45+ - (bool )testModifierKeysAreSynthesizedOnMouseMove ;
2546- (bool )testViewWillAppearCalledMultipleTimes ;
2647- (bool )testFlutterViewIsConfigured ;
2748
@@ -30,6 +51,8 @@ + (void)respondFalseForSendEvent:(const FlutterKeyEvent&)event
3051 userData : (nullable void *)userData ;
3152@end
3253
54+ using namespace ::flutter::testing::keycodes;
55+
3356namespace flutter ::testing {
3457
3558namespace {
@@ -69,6 +92,19 @@ id MockGestureEvent(NSEventType type, NSEventPhase phase, double magnification,
6992 OCMStub ([mock flagsChanged: [OCMArg any ]]).andDo (nil );
7093 return mock;
7194}
95+
96+ NSEvent * CreateMouseEvent (NSEventModifierFlags modifierFlags) {
97+ return [NSEvent mouseEventWithType: NSEventTypeMouseMoved
98+ location: NSZeroPoint
99+ modifierFlags: modifierFlags
100+ timestamp: 0
101+ windowNumber: 0
102+ context: nil
103+ eventNumber: 0
104+ clickCount: 1
105+ pressure: 1.0 ];
106+ }
107+
72108} // namespace
73109
74110TEST (FlutterViewController, HasViewThatHidesOtherViewsInAccessibility) {
@@ -161,6 +197,10 @@ id MockGestureEvent(NSEventType type, NSEventPhase phase, double magnification,
161197 ASSERT_TRUE ([[FlutterViewControllerTestObjC alloc ] testTrackpadGesturesAreSentToFramework ]);
162198}
163199
200+ TEST (FlutterViewControllerTest, TestModifierKeysAreSynthesizedOnMouseMove) {
201+ ASSERT_TRUE ([[FlutterViewControllerTestObjC alloc ] testModifierKeysAreSynthesizedOnMouseMove ]);
202+ }
203+
164204TEST (FlutterViewControllerTest, testViewWillAppearCalledMultipleTimes) {
165205 ASSERT_TRUE ([[FlutterViewControllerTestObjC alloc ] testViewWillAppearCalledMultipleTimes ]);
166206}
@@ -763,4 +803,71 @@ - (bool)testViewWillAppearCalledMultipleTimes {
763803 return true ;
764804}
765805
806+ - (bool )testModifierKeysAreSynthesizedOnMouseMove {
807+ id engineMock = OCMClassMock ([FlutterEngine class ]);
808+ // Need to return a real renderer to allow view controller to load.
809+ FlutterRenderer* renderer_ = [[FlutterRenderer alloc ] initWithFlutterEngine: engineMock];
810+ OCMStub ([engineMock renderer ]).andReturn (renderer_);
811+
812+ // Capture calls to sendKeyEvent
813+ __block NSMutableArray <KeyEventWrapper*>* events =
814+ [[NSMutableArray <KeyEventWrapper*> alloc] init ];
815+ OCMStub ([[engineMock ignoringNonObjectArgs ] sendKeyEvent: FlutterKeyEvent {}
816+ callback: nil
817+ userData: nil ])
818+ .andDo ((^(NSInvocation * invocation) {
819+ FlutterKeyEvent* event;
820+ [invocation getArgument: &event atIndex: 2 ];
821+ [events addObject: [[KeyEventWrapper alloc ] initWithEvent: event]];
822+ }));
823+
824+ FlutterViewController* viewController = [[FlutterViewController alloc ] initWithEngine: engineMock
825+ nibName: @" "
826+ bundle: nil ];
827+ [viewController loadView ];
828+ [engineMock setViewController: viewController];
829+ [viewController viewWillAppear ];
830+
831+ // Zeroed modifier flag should not synthesize events.
832+ NSEvent * mouseEvent = flutter::testing::CreateMouseEvent (0x00 );
833+ [viewController mouseMoved: mouseEvent];
834+ EXPECT_EQ ([events count ], 0u );
835+
836+ // For each modifier key, check that key events are synthesized.
837+ [flutter::keyCodeToModifierFlag
838+ enumerateKeysAndObjectsUsingBlock: ^(NSNumber * keyCode, NSNumber * flag, BOOL * stop) {
839+ FlutterKeyEvent* event;
840+ NSNumber * logicalKey;
841+ NSNumber * physicalKey;
842+
843+ // Should synthesize down event.
844+ NSEvent * mouseEvent = flutter::testing::CreateMouseEvent ([flag unsignedLongValue ]);
845+ [viewController mouseMoved: mouseEvent];
846+ EXPECT_EQ ([events count ], 1u );
847+ event = events[0 ].data ;
848+ logicalKey = [flutter::keyCodeToLogicalKey objectForKey: keyCode];
849+ physicalKey = [flutter::keyCodeToPhysicalKey objectForKey: keyCode];
850+ EXPECT_EQ (event->type , kFlutterKeyEventTypeDown );
851+ EXPECT_EQ (event->logical , logicalKey.unsignedLongLongValue );
852+ EXPECT_EQ (event->physical , physicalKey.unsignedLongLongValue );
853+ EXPECT_EQ (event->synthesized , true );
854+
855+ // Should synthesize up event.
856+ mouseEvent = flutter::testing::CreateMouseEvent (0x00 );
857+ [viewController mouseMoved: mouseEvent];
858+ EXPECT_EQ ([events count ], 2u );
859+ event = events[1 ].data ;
860+ logicalKey = [flutter::keyCodeToLogicalKey objectForKey: keyCode];
861+ physicalKey = [flutter::keyCodeToPhysicalKey objectForKey: keyCode];
862+ EXPECT_EQ (event->type , kFlutterKeyEventTypeUp );
863+ EXPECT_EQ (event->logical , logicalKey.unsignedLongLongValue );
864+ EXPECT_EQ (event->physical , physicalKey.unsignedLongLongValue );
865+ EXPECT_EQ (event->synthesized , true );
866+
867+ [events removeAllObjects ];
868+ }];
869+
870+ return true ;
871+ }
872+
766873@end
0 commit comments