@@ -250,60 +250,6 @@ - (bool)testComposingRegionRemovedByFramework {
250250 return true ;
251251}
252252
253- - (bool )testInputContextIsKeptActive {
254- id engineMock = OCMClassMock ([FlutterEngine class ]);
255- FlutterViewController* viewController = [[FlutterViewController alloc ] initWithEngine: engineMock
256- nibName: @" "
257- bundle: nil ];
258-
259- FlutterTextInputPlugin* plugin =
260- [[FlutterTextInputPlugin alloc ] initWithViewController: viewController];
261-
262- [plugin handleMethodCall: [FlutterMethodCall
263- methodCallWithMethodName: @" TextInput.setClient"
264- arguments: @[
265- @(1 ), @{
266- @" inputAction" : @" action" ,
267- @" inputType" : @{@" name" : @" inputName" },
268- }
269- ]]
270- result: ^(id ){
271- }];
272-
273- [plugin handleMethodCall: [FlutterMethodCall methodCallWithMethodName: @" TextInput.setEditingState"
274- arguments: @{
275- @" text" : @" " ,
276- @" selectionBase" : @(0 ),
277- @" selectionExtent" : @(0 ),
278- @" composingBase" : @(-1 ),
279- @" composingExtent" : @(-1 ),
280- }]
281- result: ^(id ){
282- }];
283-
284- [plugin handleMethodCall: [FlutterMethodCall methodCallWithMethodName: @" TextInput.show"
285- arguments: @[]]
286- result: ^(id ){
287- }];
288-
289- [plugin.inputContext deactivate ];
290- EXPECT_EQ (plugin.inputContext .isActive , NO );
291- NSEvent * keyEvent = [NSEvent keyEventWithType: NSEventTypeKeyDown
292- location: NSZeroPoint
293- modifierFlags: 0x100
294- timestamp: 0
295- windowNumber: 0
296- context: nil
297- characters: @" "
298- charactersIgnoringModifiers: @" "
299- isARepeat: NO
300- keyCode: 0x50 ];
301-
302- [plugin handleKeyEvent: keyEvent];
303- EXPECT_EQ (plugin.inputContext .isActive , YES );
304- return true ;
305- }
306-
307253- (bool )testClearClientDuringComposing {
308254 // Set up FlutterTextInputPlugin.
309255 id engineMock = OCMClassMock ([FlutterEngine class ]);
@@ -917,6 +863,63 @@ - (bool)testComposingWithDeltasWhenSelectionIsActive {
917863 return true ;
918864}
919865
866+ - (bool )testPerformKeyEquivalent {
867+ __block NSEvent * eventBeingDispatchedByKeyboardManager = nil ;
868+ FlutterViewController* viewControllerMock = OCMClassMock ([FlutterViewController class ]);
869+ OCMStub ([viewControllerMock isDispatchingKeyEvent: [OCMArg any ]])
870+ .andDo (^(NSInvocation * invocation) {
871+ NSEvent * event;
872+ [invocation getArgument: (void *)&event atIndex: 2 ];
873+ BOOL result = event == eventBeingDispatchedByKeyboardManager;
874+ [invocation setReturnValue: &result];
875+ });
876+
877+ NSEvent * event = [NSEvent keyEventWithType: NSEventTypeKeyDown
878+ location: NSZeroPoint
879+ modifierFlags: 0x100
880+ timestamp: 0
881+ windowNumber: 0
882+ context: nil
883+ characters: @" "
884+ charactersIgnoringModifiers: @" "
885+ isARepeat: NO
886+ keyCode: 0x50 ];
887+
888+ FlutterTextInputPlugin* plugin =
889+ [[FlutterTextInputPlugin alloc ] initWithViewController: viewControllerMock];
890+
891+ OCMExpect ([viewControllerMock keyDown: event]);
892+
893+ // Require that event is handled (returns YES)
894+ if (![plugin performKeyEquivalent: event]) {
895+ return false ;
896+ };
897+
898+ @try {
899+ OCMVerify ( // NOLINT(google-objc-avoid-throwing-exception)
900+ [viewControllerMock keyDown: event]);
901+ } @catch (...) {
902+ return false ;
903+ }
904+
905+ // performKeyEquivalent must not forward event if it is being
906+ // dispatched by keyboard manager
907+ eventBeingDispatchedByKeyboardManager = event;
908+
909+ OCMReject ([viewControllerMock keyDown: event]);
910+ @try {
911+ // Require that event is not handled (returns NO) and not
912+ // forwarded to controller
913+ if ([plugin performKeyEquivalent: event]) {
914+ return false ;
915+ };
916+ } @catch (...) {
917+ return false ;
918+ }
919+
920+ return true ;
921+ }
922+
920923- (bool )testLocalTextAndSelectionUpdateAfterDelta {
921924 id engineMock = OCMClassMock ([FlutterEngine class ]);
922925 id binaryMessengerMock = OCMProtocolMock (@protocol (FlutterBinaryMessenger));
@@ -1005,10 +1008,6 @@ - (bool)testLocalTextAndSelectionUpdateAfterDelta {
10051008 ASSERT_TRUE ([[FlutterInputPluginTestObjc alloc ] testComposingRegionRemovedByFramework ]);
10061009}
10071010
1008- TEST (FlutterTextInputPluginTest, TestTextInputContextIsKeptAlive) {
1009- ASSERT_TRUE ([[FlutterInputPluginTestObjc alloc ] testInputContextIsKeptActive ]);
1010- }
1011-
10121011TEST (FlutterTextInputPluginTest, TestClearClientDuringComposing) {
10131012 ASSERT_TRUE ([[FlutterInputPluginTestObjc alloc ] testClearClientDuringComposing ]);
10141013}
@@ -1037,6 +1036,10 @@ - (bool)testLocalTextAndSelectionUpdateAfterDelta {
10371036 ASSERT_TRUE ([[FlutterInputPluginTestObjc alloc ] testLocalTextAndSelectionUpdateAfterDelta ]);
10381037}
10391038
1039+ TEST (FlutterTextInputPluginTest, TestPerformKeyEquivalent) {
1040+ ASSERT_TRUE ([[FlutterInputPluginTestObjc alloc ] testPerformKeyEquivalent ]);
1041+ }
1042+
10401043TEST (FlutterTextInputPluginTest, CanWorkWithFlutterTextField) {
10411044 FlutterEngine* engine = CreateTestEngine ();
10421045 NSString * fixtures = @(testing::GetFixturesPath ());
@@ -1069,7 +1072,7 @@ - (bool)testLocalTextAndSelectionUpdateAfterDelta {
10691072 [[FlutterTextFieldMock alloc ] initWithPlatformNode: &text_platform_node
10701073 fieldEditor: viewController.textInputPlugin];
10711074 [viewController.view addSubview: mockTextField];
1072- [mockTextField becomeFirstResponder ];
1075+ [mockTextField startEditing ];
10731076
10741077 NSDictionary * arguments = @{
10751078 @" inputAction" : @" action" ,
@@ -1133,4 +1136,40 @@ - (bool)testLocalTextAndSelectionUpdateAfterDelta {
11331136 EXPECT_EQ ([textField becomeFirstResponder ], NO );
11341137}
11351138
1139+ TEST (FlutterTextInputPluginTest, IsAddedAndRemovedFromViewHierarchy) {
1140+ FlutterEngine* engine = CreateTestEngine ();
1141+ NSString * fixtures = @(testing::GetFixturesPath ());
1142+ FlutterDartProject* project = [[FlutterDartProject alloc ]
1143+ initWithAssetsPath: fixtures
1144+ ICUDataPath: [fixtures stringByAppendingString: @" /icudtl.dat" ]];
1145+ FlutterViewController* viewController = [[FlutterViewController alloc ] initWithProject: project];
1146+ [viewController loadView ];
1147+ [engine setViewController: viewController];
1148+
1149+ NSWindow * window = [[NSWindow alloc ] initWithContentRect: NSMakeRect (0 , 0 , 800 , 600 )
1150+ styleMask: NSBorderlessWindowMask
1151+ backing: NSBackingStoreBuffered
1152+ defer: NO ];
1153+ window.contentView = viewController.view ;
1154+
1155+ ASSERT_EQ (viewController.textInputPlugin .superview , nil );
1156+ ASSERT_FALSE (window.firstResponder == viewController.textInputPlugin );
1157+
1158+ [viewController.textInputPlugin
1159+ handleMethodCall: [FlutterMethodCall methodCallWithMethodName: @" TextInput.show" arguments: @[]]
1160+ result: ^(id ){
1161+ }];
1162+
1163+ ASSERT_EQ (viewController.textInputPlugin .superview , viewController.view );
1164+ ASSERT_TRUE (window.firstResponder == viewController.textInputPlugin );
1165+
1166+ [viewController.textInputPlugin
1167+ handleMethodCall: [FlutterMethodCall methodCallWithMethodName: @" TextInput.hide" arguments: @[]]
1168+ result: ^(id ){
1169+ }];
1170+
1171+ ASSERT_EQ (viewController.textInputPlugin .superview , nil );
1172+ ASSERT_FALSE (window.firstResponder == viewController.textInputPlugin );
1173+ }
1174+
11361175} // namespace flutter::testing
0 commit comments