diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index e968f9fd6854f..5d0fcecc0e90b 100755 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -1148,6 +1148,7 @@ FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterSurfa FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterSurfaceManager.mm FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterTextInputPlugin.h FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterTextInputPlugin.mm +FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterTextInputPluginTest.mm FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterTextureRegistrar.h FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterTextureRegistrar.mm FILE: ../../../flutter/shell/platform/darwin/macos/framework/Source/FlutterView.h diff --git a/shell/platform/darwin/macos/BUILD.gn b/shell/platform/darwin/macos/BUILD.gn index 404ebc57303d8..e8e2223ce0286 100644 --- a/shell/platform/darwin/macos/BUILD.gn +++ b/shell/platform/darwin/macos/BUILD.gn @@ -165,6 +165,7 @@ executable("flutter_desktop_darwin_unittests") { "framework/Source/FlutterMetalSurfaceManagerTest.mm", "framework/Source/FlutterOpenGLRendererTest.mm", "framework/Source/FlutterPlatformNodeDelegateMacTest.mm", + "framework/Source/FlutterTextInputPluginTest.mm", "framework/Source/FlutterViewControllerTest.mm", "framework/Source/FlutterViewControllerTestUtils.h", "framework/Source/FlutterViewControllerTestUtils.mm", diff --git a/shell/platform/darwin/macos/framework/Source/FlutterTextInputPlugin.h b/shell/platform/darwin/macos/framework/Source/FlutterTextInputPlugin.h index 2f4a5436e74d7..f82be5568a283 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterTextInputPlugin.h +++ b/shell/platform/darwin/macos/framework/Source/FlutterTextInputPlugin.h @@ -25,3 +25,8 @@ - (instancetype)initWithViewController:(FlutterViewController*)viewController; @end + +// Private methods made visible for testing +@interface FlutterTextInputPlugin (TestMethods) +- (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result; +@end diff --git a/shell/platform/darwin/macos/framework/Source/FlutterTextInputPlugin.mm b/shell/platform/darwin/macos/framework/Source/FlutterTextInputPlugin.mm index 159d7c74e52f9..4161c6b2d137e 100644 --- a/shell/platform/darwin/macos/framework/Source/FlutterTextInputPlugin.mm +++ b/shell/platform/darwin/macos/framework/Source/FlutterTextInputPlugin.mm @@ -227,13 +227,17 @@ - (void)updateEditState { NSString* const textAffinity = (self.textAffinity == FlutterTextAffinityUpstream) ? kTextAffinityUpstream : kTextAffinityDownstream; + + int composingBase = _activeModel->composing() ? _activeModel->composing_range().base() : -1; + int composingExtent = _activeModel->composing() ? _activeModel->composing_range().extent() : -1; + NSDictionary* state = @{ kSelectionBaseKey : @(_activeModel->selection().base()), kSelectionExtentKey : @(_activeModel->selection().extent()), kSelectionAffinityKey : textAffinity, kSelectionIsDirectionalKey : @NO, - kComposingBaseKey : @(_activeModel->composing_range().base()), - kComposingExtentKey : @(_activeModel->composing_range().extent()), + kComposingBaseKey : @(composingBase), + kComposingExtentKey : @(composingExtent), kTextKey : [NSString stringWithUTF8String:_activeModel->GetText().c_str()] }; diff --git a/shell/platform/darwin/macos/framework/Source/FlutterTextInputPluginTest.mm b/shell/platform/darwin/macos/framework/Source/FlutterTextInputPluginTest.mm new file mode 100644 index 0000000000000..5712b1615b60f --- /dev/null +++ b/shell/platform/darwin/macos/framework/Source/FlutterTextInputPluginTest.mm @@ -0,0 +1,92 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#import "flutter/shell/platform/darwin/macos/framework/Headers/FlutterViewController.h" +#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterDartProject_Internal.h" +#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterTextInputPlugin.h" +#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterViewController_Internal.h" + +#import +#import "flutter/testing/testing.h" + +@interface FlutterInputPluginTestObjc : NSObject +- (bool)testEmptyCompositionRange; +@end + +@implementation FlutterInputPluginTestObjc + +- (bool)testEmptyCompositionRange { + id engineMock = OCMClassMock([FlutterEngine class]); + id binaryMessengerMock = OCMProtocolMock(@protocol(FlutterBinaryMessenger)); + OCMStub( // NOLINT(google-objc-avoid-throwing-exception) + [engineMock binaryMessenger]) + .andReturn(binaryMessengerMock); + + FlutterViewController* viewController = [[FlutterViewController alloc] initWithEngine:engineMock + nibName:@"" + bundle:nil]; + + FlutterTextInputPlugin* plugin = + [[FlutterTextInputPlugin alloc] initWithViewController:viewController]; + + [plugin handleMethodCall:[FlutterMethodCall + methodCallWithMethodName:@"TextInput.setClient" + arguments:@[ + @(1), @{ + @"inputAction" : @"action", + @"inputType" : @{@"name" : @"inputName"}, + } + ]] + result:^(id){ + }]; + + FlutterMethodCall* call = [FlutterMethodCall methodCallWithMethodName:@"TextInput.setEditingState" + arguments:@{ + @"text" : @"Text", + @"selectionBase" : @(0), + @"selectionExtent" : @(0), + @"composingBase" : @(-1), + @"composingExtent" : @(-1), + }]; + + NSDictionary* expectedState = @{ + @"selectionBase" : @(0), + @"selectionExtent" : @(0), + @"selectionAffinity" : @"TextAffinity.upstream", + @"selectionIsDirectional" : @(NO), + @"composingBase" : @(-1), + @"composingExtent" : @(-1), + @"text" : @"Text", + }; + + NSData* updateCall = [[FlutterJSONMethodCodec sharedInstance] + encodeMethodCall:[FlutterMethodCall + methodCallWithMethodName:@"TextInputClient.updateEditingState" + arguments:@[ @(1), expectedState ]]]; + + OCMExpect( // NOLINT(google-objc-avoid-throwing-exception) + [binaryMessengerMock sendOnChannel:@"flutter/textinput" message:updateCall]); + + [plugin handleMethodCall:call + result:^(id){ + }]; + + @try { + OCMVerify( // NOLINT(google-objc-avoid-throwing-exception) + [binaryMessengerMock sendOnChannel:@"flutter/textinput" message:updateCall]); + } @catch (...) { + return false; + } + return true; +} + +@end + +namespace flutter::testing { + +TEST(FlutterTextInputPluginTest, TestEmptyCompositionRange) { + ASSERT_TRUE([[FlutterInputPluginTestObjc alloc] testEmptyCompositionRange]); +} + +} // namespace flutter::testing