14 #import <OCMock/OCMock.h>
15 #import "flutter/testing/testing.h"
18 - (void)setPlatformNode:(
flutter::FlutterTextPlatformNode*)node;
23 @property(nonatomic, nullable, copy) NSString* lastUpdatedString;
24 @property(nonatomic) NSRange lastUpdatedSelection;
30 - (void)updateString:(NSString*)string withSelection:(NSRange)selection {
31 _lastUpdatedString = string;
32 _lastUpdatedSelection = selection;
46 - (nonnull
FlutterView*)createFlutterViewWithMTLDevice:(id<MTLDevice>)device
47 commandQueue:(id<MTLCommandQueue>)commandQueue {
48 return OCMClassMock([NSView
class]);
63 [engineMock binaryMessenger])
64 .andReturn(binaryMessengerMock);
73 NSDictionary* setClientConfig = @{
74 @"inputAction" :
@"action",
75 @"inputType" : @{
@"name" :
@"inputName"},
85 @"selectionBase" : @(0),
86 @"selectionExtent" : @(0),
87 @"composingBase" : @(-1),
88 @"composingExtent" : @(-1),
97 EXPECT_STREQ([editingState[
@"text"] UTF8String],
"Text");
98 EXPECT_STREQ([editingState[
@"selectionAffinity"] UTF8String],
"TextAffinity.upstream");
99 EXPECT_FALSE([editingState[
@"selectionIsDirectional"] boolValue]);
100 EXPECT_EQ([editingState[
@"selectionBase"] intValue], 0);
101 EXPECT_EQ([editingState[
@"selectionExtent"] intValue], 0);
102 EXPECT_EQ([editingState[
@"composingBase"] intValue], -1);
103 EXPECT_EQ([editingState[
@"composingExtent"] intValue], -1);
107 - (bool)testSetMarkedTextWithSelectionChange {
111 [engineMock binaryMessenger])
112 .andReturn(binaryMessengerMock);
121 NSDictionary* setClientConfig = @{
122 @"inputAction" :
@"action",
123 @"inputType" : @{
@"name" :
@"inputName"},
133 @"selectionBase" : @(4),
134 @"selectionExtent" : @(4),
135 @"composingBase" : @(-1),
136 @"composingExtent" : @(-1),
142 [plugin setMarkedText:@"marked"
143 selectedRange:NSMakeRange(1, 0)
144 replacementRange:NSMakeRange(NSNotFound, 0)];
146 NSDictionary* expectedState = @{
147 @"selectionBase" : @(5),
148 @"selectionExtent" : @(5),
149 @"selectionAffinity" :
@"TextAffinity.upstream",
150 @"selectionIsDirectional" : @(NO),
151 @"composingBase" : @(4),
152 @"composingExtent" : @(10),
153 @"text" :
@"Textmarked",
162 [binaryMessengerMock sendOnChannel:
@"flutter/textinput" message:updateCall]);
166 [binaryMessengerMock sendOnChannel:
@"flutter/textinput" message:updateCall]);
173 - (bool)testSetMarkedTextWithReplacementRange {
177 [engineMock binaryMessenger])
178 .andReturn(binaryMessengerMock);
187 NSDictionary* setClientConfig = @{
188 @"inputAction" :
@"action",
189 @"inputType" : @{
@"name" :
@"inputName"},
199 @"selectionBase" : @(3),
200 @"selectionExtent" : @(3),
201 @"composingBase" : @(-1),
202 @"composingExtent" : @(-1),
208 [plugin setMarkedText:@"marked"
209 selectedRange:NSMakeRange(1, 0)
210 replacementRange:NSMakeRange(1, 2)];
212 NSDictionary* expectedState = @{
213 @"selectionBase" : @(2),
214 @"selectionExtent" : @(2),
215 @"selectionAffinity" :
@"TextAffinity.upstream",
216 @"selectionIsDirectional" : @(NO),
217 @"composingBase" : @(1),
218 @"composingExtent" : @(7),
219 @"text" :
@"1marked4",
228 [binaryMessengerMock sendOnChannel:
@"flutter/textinput" message:updateCall]);
232 [binaryMessengerMock sendOnChannel:
@"flutter/textinput" message:updateCall]);
239 - (bool)testComposingRegionRemovedByFramework {
243 [engineMock binaryMessenger])
244 .andReturn(binaryMessengerMock);
253 NSDictionary* setClientConfig = @{
254 @"inputAction" :
@"action",
255 @"inputType" : @{
@"name" :
@"inputName"},
265 @"selectionBase" : @(4),
266 @"selectionExtent" : @(4),
267 @"composingBase" : @(2),
268 @"composingExtent" : @(4),
278 @"selectionBase" : @(2),
279 @"selectionExtent" : @(2),
280 @"composingBase" : @(-1),
281 @"composingExtent" : @(-1),
289 EXPECT_STREQ([editingState[
@"text"] UTF8String],
"Te");
290 EXPECT_STREQ([editingState[
@"selectionAffinity"] UTF8String],
"TextAffinity.upstream");
291 EXPECT_FALSE([editingState[
@"selectionIsDirectional"] boolValue]);
292 EXPECT_EQ([editingState[
@"selectionBase"] intValue], 2);
293 EXPECT_EQ([editingState[
@"selectionExtent"] intValue], 2);
294 EXPECT_EQ([editingState[
@"composingBase"] intValue], -1);
295 EXPECT_EQ([editingState[
@"composingExtent"] intValue], -1);
304 [engineMock binaryMessenger])
305 .andReturn(binaryMessengerMock);
313 NSDictionary* setClientConfig = @{
314 @"inputAction" :
@"action",
315 @"inputType" : @{
@"name" :
@"inputName"},
326 @"selectionBase" : @(0),
327 @"selectionExtent" : @(0),
328 @"composingBase" : @(0),
329 @"composingExtent" : @(1),
336 EXPECT_EQ([editingState[
@"composingBase"] intValue], 0);
337 EXPECT_EQ([editingState[
@"composingExtent"] intValue], 1);
347 EXPECT_EQ([editingState[
@"composingBase"] intValue], [editingState[
@"composingExtent"] intValue]);
351 - (bool)testAutocompleteDisabledWhenAutofillNotSet {
356 [engineMock binaryMessenger])
357 .andReturn(binaryMessengerMock);
365 NSDictionary* setClientConfig = @{
366 @"inputAction" :
@"action",
367 @"inputType" : @{
@"name" :
@"inputName"},
375 EXPECT_FALSE([plugin isAutomaticTextCompletionEnabled]);
379 - (bool)testAutocompleteEnabledWhenAutofillSet {
384 [engineMock binaryMessenger])
385 .andReturn(binaryMessengerMock);
393 NSDictionary* setClientConfig = @{
394 @"inputAction" :
@"action",
395 @"inputType" : @{
@"name" :
@"inputName"},
397 @"uniqueIdentifier" :
@"field1",
398 @"hints" : @[ @"name" ],
399 @"editingValue" : @{
@"text" :
@""},
408 EXPECT_TRUE([plugin isAutomaticTextCompletionEnabled]);
411 if (@available(macOS 11.0, *)) {
412 EXPECT_EQ([plugin contentType], nil);
417 - (bool)testAutocompleteEnabledWhenAutofillSetNoHint {
422 [engineMock binaryMessenger])
423 .andReturn(binaryMessengerMock);
431 NSDictionary* setClientConfig = @{
432 @"inputAction" :
@"action",
433 @"inputType" : @{
@"name" :
@"inputName"},
435 @"uniqueIdentifier" :
@"field1",
437 @"editingValue" : @{
@"text" :
@""},
446 EXPECT_TRUE([plugin isAutomaticTextCompletionEnabled]);
450 - (bool)testAutocompleteDisabledWhenObscureTextSet {
455 [engineMock binaryMessenger])
456 .andReturn(binaryMessengerMock);
464 NSDictionary* setClientConfig = @{
465 @"inputAction" :
@"action",
466 @"inputType" : @{
@"name" :
@"inputName"},
467 @"obscureText" : @YES,
469 @"uniqueIdentifier" :
@"field1",
470 @"editingValue" : @{
@"text" :
@""},
479 EXPECT_FALSE([plugin isAutomaticTextCompletionEnabled]);
483 - (bool)testAutocompleteDisabledWhenPasswordAutofillSet {
488 [engineMock binaryMessenger])
489 .andReturn(binaryMessengerMock);
497 NSDictionary* setClientConfig = @{
498 @"inputAction" :
@"action",
499 @"inputType" : @{
@"name" :
@"inputName"},
501 @"uniqueIdentifier" :
@"field1",
502 @"hints" : @[ @"password" ],
503 @"editingValue" : @{
@"text" :
@""},
512 EXPECT_FALSE([plugin isAutomaticTextCompletionEnabled]);
515 if (@available(macOS 11.0, *)) {
516 EXPECT_EQ([plugin contentType], NSTextContentTypePassword);
521 - (bool)testAutocompleteDisabledWhenAutofillGroupIncludesPassword {
526 [engineMock binaryMessenger])
527 .andReturn(binaryMessengerMock);
535 NSDictionary* setClientConfig = @{
536 @"inputAction" :
@"action",
537 @"inputType" : @{
@"name" :
@"inputName"},
540 @"inputAction" : @"action",
541 @"inputType" : @{@"name" : @"inputName"},
543 @"uniqueIdentifier" : @"field1",
544 @"hints" : @[ @"password" ],
545 @"editingValue" : @{@"text" : @""},
549 @"inputAction" : @"action",
550 @"inputType" : @{@"name" : @"inputName"},
552 @"uniqueIdentifier" : @"field2",
553 @"hints" : @[ @"name" ],
554 @"editingValue" : @{@"text" : @""},
565 EXPECT_FALSE([plugin isAutomaticTextCompletionEnabled]);
569 - (bool)testContentTypeWhenAutofillTypeIsUsername {
574 [engineMock binaryMessenger])
575 .andReturn(binaryMessengerMock);
583 NSDictionary* setClientConfig = @{
584 @"inputAction" :
@"action",
585 @"inputType" : @{
@"name" :
@"inputName"},
587 @"uniqueIdentifier" :
@"field1",
588 @"hints" : @[ @"name" ],
589 @"editingValue" : @{
@"text" :
@""},
598 EXPECT_FALSE([plugin isAutomaticTextCompletionEnabled]);
601 if (@available(macOS 11.0, *)) {
602 EXPECT_EQ([plugin contentType], NSTextContentTypeUsername);
607 - (bool)testContentTypeWhenAutofillTypeIsOneTimeCode {
612 [engineMock binaryMessenger])
613 .andReturn(binaryMessengerMock);
621 NSDictionary* setClientConfig = @{
622 @"inputAction" :
@"action",
623 @"inputType" : @{
@"name" :
@"inputName"},
625 @"uniqueIdentifier" :
@"field1",
626 @"hints" : @[ @"oneTimeCode" ],
627 @"editingValue" : @{
@"text" :
@""},
636 EXPECT_FALSE([plugin isAutomaticTextCompletionEnabled]);
639 if (@available(macOS 11.0, *)) {
640 EXPECT_EQ([plugin contentType], NSTextContentTypeOneTimeCode);
645 - (bool)testFirstRectForCharacterRange {
649 [engineMock binaryMessenger])
650 .andReturn(binaryMessengerMock);
653 [controllerMock loadView];
654 id viewMock = controllerMock.flutterView;
657 .andReturn(NSMakeRect(0, 0, 200, 200));
659 id windowMock = OCMClassMock([NSWindow
class]);
662 .andReturn(windowMock);
665 [viewMock convertRect:NSMakeRect(28, 10, 2, 19) toView:nil])
666 .andReturn(NSMakeRect(28, 10, 2, 19));
669 [windowMock convertRectToScreen:NSMakeRect(28, 10, 2, 19)])
670 .andReturn(NSMakeRect(38, 20, 2, 19));
680 @(1.0), @(0.0), @(0.0), @(0.0), @(0.0), @(1.0), @(0.0), @(0.0), @(0.0),
681 @(0.0), @(1.0), @(0.0), @(20.0), @(10.0), @(0.0), @(1.0)
705 [windowMock convertRectToScreen:NSMakeRect(28, 10, 2, 19)]);
710 return NSEqualRects(rect, NSMakeRect(38, 20, 2, 19));
713 - (bool)testFirstRectForCharacterRangeAtInfinity {
717 [engineMock binaryMessenger])
718 .andReturn(binaryMessengerMock);
721 [controllerMock loadView];
722 id viewMock = controllerMock.flutterView;
725 .andReturn(NSMakeRect(0, 0, 200, 200));
727 id windowMock = OCMClassMock([NSWindow
class]);
730 .andReturn(windowMock);
741 @(1.0), @(0.0), @(0.0), @(0.0), @(0.0), @(1.0), @(0.0), @(0.0), @(0.0),
742 @(0.0), @(1.0), @(0.0), @(20.0), @(10.0), @(0.0), @(0.0)
764 return NSEqualRects(rect, CGRectZero);
767 - (bool)testFirstRectForCharacterRangeWithEsotericAffineTransform {
771 [engineMock binaryMessenger])
772 .andReturn(binaryMessengerMock);
775 [controllerMock loadView];
776 id viewMock = controllerMock.flutterView;
779 .andReturn(NSMakeRect(0, 0, 200, 200));
781 id windowMock = OCMClassMock([NSWindow
class]);
784 .andReturn(windowMock);
787 [viewMock convertRect:NSMakeRect(-18, 6, 3, 3) toView:nil])
788 .andReturn(NSMakeRect(-18, 6, 3, 3));
791 [windowMock convertRectToScreen:NSMakeRect(-18, 6, 3, 3)])
792 .andReturn(NSMakeRect(-18, 6, 3, 3));
805 @(0.0), @(3.0), @(0.0), @(0.0), @(-3.0), @(0.0), @(0.0), @(0.0), @(0.0),
806 @(0.0), @(3.0), @(0.0), @(-6.0), @(3.0), @(9.0), @(1.0)
831 [windowMock convertRectToScreen:NSMakeRect(-18, 6, 3, 3)]);
836 return NSEqualRects(rect, NSMakeRect(-18, 6, 3, 3));
839 - (bool)testSetEditingStateWithTextEditingDelta {
843 [engineMock binaryMessenger])
844 .andReturn(binaryMessengerMock);
853 NSDictionary* setClientConfig = @{
854 @"inputAction" :
@"action",
855 @"enableDeltaModel" :
@"true",
856 @"inputType" : @{
@"name" :
@"inputName"},
866 @"selectionBase" : @(0),
867 @"selectionExtent" : @(0),
868 @"composingBase" : @(-1),
869 @"composingExtent" : @(-1),
878 EXPECT_STREQ([editingState[
@"text"] UTF8String],
"Text");
879 EXPECT_STREQ([editingState[
@"selectionAffinity"] UTF8String],
"TextAffinity.upstream");
880 EXPECT_FALSE([editingState[
@"selectionIsDirectional"] boolValue]);
881 EXPECT_EQ([editingState[
@"selectionBase"] intValue], 0);
882 EXPECT_EQ([editingState[
@"selectionExtent"] intValue], 0);
883 EXPECT_EQ([editingState[
@"composingBase"] intValue], -1);
884 EXPECT_EQ([editingState[
@"composingExtent"] intValue], -1);
888 - (bool)testOperationsThatTriggerDelta {
892 [engineMock binaryMessenger])
893 .andReturn(binaryMessengerMock);
902 NSDictionary* setClientConfig = @{
903 @"inputAction" :
@"action",
904 @"enableDeltaModel" :
@"true",
905 @"inputType" : @{
@"name" :
@"inputName"},
911 [plugin insertText:@"text to insert"];
913 NSDictionary* deltaToFramework = @{
915 @"deltaText" :
@"text to insert",
916 @"deltaStart" : @(0),
918 @"selectionBase" : @(14),
919 @"selectionExtent" : @(14),
920 @"selectionAffinity" :
@"TextAffinity.upstream",
921 @"selectionIsDirectional" : @(
false),
922 @"composingBase" : @(-1),
923 @"composingExtent" : @(-1),
925 NSDictionary* expectedState = @{
926 @"deltas" : @[ deltaToFramework ],
936 [binaryMessengerMock sendOnChannel:
@"flutter/textinput" message:updateCall]);
941 [plugin setMarkedText:@"marked text" selectedRange:NSMakeRange(0, 1)];
943 deltaToFramework = @{
944 @"oldText" :
@"text to insert",
945 @"deltaText" :
@"marked text",
946 @"deltaStart" : @(14),
948 @"selectionBase" : @(14),
949 @"selectionExtent" : @(15),
950 @"selectionAffinity" :
@"TextAffinity.upstream",
951 @"selectionIsDirectional" : @(
false),
952 @"composingBase" : @(14),
953 @"composingExtent" : @(25),
956 @"deltas" : @[ deltaToFramework ],
966 [binaryMessengerMock sendOnChannel:
@"flutter/textinput" message:updateCall]);
973 deltaToFramework = @{
974 @"oldText" :
@"text to insertmarked text",
976 @"deltaStart" : @(-1),
978 @"selectionBase" : @(25),
979 @"selectionExtent" : @(25),
980 @"selectionAffinity" :
@"TextAffinity.upstream",
981 @"selectionIsDirectional" : @(false),
982 @"composingBase" : @(-1),
983 @"composingExtent" : @(-1),
986 @"deltas" : @[ deltaToFramework ],
996 [binaryMessengerMock sendOnChannel:
@"flutter/textinput" message:updateCall]);
1003 - (bool)testComposingWithDelta {
1007 [engineMock binaryMessenger])
1008 .andReturn(binaryMessengerMock);
1017 NSDictionary* setClientConfig = @{
1018 @"inputAction" :
@"action",
1019 @"enableDeltaModel" :
@"true",
1020 @"inputType" : @{
@"name" :
@"inputName"},
1026 [plugin setMarkedText:@"m" selectedRange:NSMakeRange(0, 1)];
1028 NSDictionary* deltaToFramework = @{
1030 @"deltaText" :
@"m",
1031 @"deltaStart" : @(0),
1033 @"selectionBase" : @(0),
1034 @"selectionExtent" : @(1),
1035 @"selectionAffinity" :
@"TextAffinity.upstream",
1036 @"selectionIsDirectional" : @(
false),
1037 @"composingBase" : @(0),
1038 @"composingExtent" : @(1),
1040 NSDictionary* expectedState = @{
1041 @"deltas" : @[ deltaToFramework ],
1051 [binaryMessengerMock sendOnChannel:
@"flutter/textinput" message:updateCall]);
1056 [plugin setMarkedText:@"ma" selectedRange:NSMakeRange(0, 1)];
1058 deltaToFramework = @{
1060 @"deltaText" :
@"ma",
1061 @"deltaStart" : @(0),
1063 @"selectionBase" : @(0),
1064 @"selectionExtent" : @(1),
1065 @"selectionAffinity" :
@"TextAffinity.upstream",
1066 @"selectionIsDirectional" : @(
false),
1067 @"composingBase" : @(0),
1068 @"composingExtent" : @(2),
1071 @"deltas" : @[ deltaToFramework ],
1081 [binaryMessengerMock sendOnChannel:
@"flutter/textinput" message:updateCall]);
1086 [plugin setMarkedText:@"mar" selectedRange:NSMakeRange(0, 1)];
1088 deltaToFramework = @{
1090 @"deltaText" :
@"mar",
1091 @"deltaStart" : @(0),
1093 @"selectionBase" : @(0),
1094 @"selectionExtent" : @(1),
1095 @"selectionAffinity" :
@"TextAffinity.upstream",
1096 @"selectionIsDirectional" : @(
false),
1097 @"composingBase" : @(0),
1098 @"composingExtent" : @(3),
1101 @"deltas" : @[ deltaToFramework ],
1111 [binaryMessengerMock sendOnChannel:
@"flutter/textinput" message:updateCall]);
1116 [plugin setMarkedText:@"mark" selectedRange:NSMakeRange(0, 1)];
1118 deltaToFramework = @{
1119 @"oldText" :
@"mar",
1120 @"deltaText" :
@"mark",
1121 @"deltaStart" : @(0),
1123 @"selectionBase" : @(0),
1124 @"selectionExtent" : @(1),
1125 @"selectionAffinity" :
@"TextAffinity.upstream",
1126 @"selectionIsDirectional" : @(
false),
1127 @"composingBase" : @(0),
1128 @"composingExtent" : @(4),
1131 @"deltas" : @[ deltaToFramework ],
1141 [binaryMessengerMock sendOnChannel:
@"flutter/textinput" message:updateCall]);
1146 [plugin setMarkedText:@"marke" selectedRange:NSMakeRange(0, 1)];
1148 deltaToFramework = @{
1149 @"oldText" :
@"mark",
1150 @"deltaText" :
@"marke",
1151 @"deltaStart" : @(0),
1153 @"selectionBase" : @(0),
1154 @"selectionExtent" : @(1),
1155 @"selectionAffinity" :
@"TextAffinity.upstream",
1156 @"selectionIsDirectional" : @(
false),
1157 @"composingBase" : @(0),
1158 @"composingExtent" : @(5),
1161 @"deltas" : @[ deltaToFramework ],
1171 [binaryMessengerMock sendOnChannel:
@"flutter/textinput" message:updateCall]);
1176 [plugin setMarkedText:@"marked" selectedRange:NSMakeRange(0, 1)];
1178 deltaToFramework = @{
1179 @"oldText" :
@"marke",
1180 @"deltaText" :
@"marked",
1181 @"deltaStart" : @(0),
1183 @"selectionBase" : @(0),
1184 @"selectionExtent" : @(1),
1185 @"selectionAffinity" :
@"TextAffinity.upstream",
1186 @"selectionIsDirectional" : @(
false),
1187 @"composingBase" : @(0),
1188 @"composingExtent" : @(6),
1191 @"deltas" : @[ deltaToFramework ],
1201 [binaryMessengerMock sendOnChannel:
@"flutter/textinput" message:updateCall]);
1206 [plugin unmarkText];
1208 deltaToFramework = @{
1209 @"oldText" :
@"marked",
1211 @"deltaStart" : @(-1),
1212 @"deltaEnd" : @(-1),
1213 @"selectionBase" : @(6),
1214 @"selectionExtent" : @(6),
1215 @"selectionAffinity" :
@"TextAffinity.upstream",
1216 @"selectionIsDirectional" : @(false),
1217 @"composingBase" : @(-1),
1218 @"composingExtent" : @(-1),
1221 @"deltas" : @[ deltaToFramework ],
1231 [binaryMessengerMock sendOnChannel:
@"flutter/textinput" message:updateCall]);
1238 - (bool)testComposingWithDeltasWhenSelectionIsActive {
1242 [engineMock binaryMessenger])
1243 .andReturn(binaryMessengerMock);
1252 NSDictionary* setClientConfig = @{
1253 @"inputAction" :
@"action",
1254 @"enableDeltaModel" :
@"true",
1255 @"inputType" : @{
@"name" :
@"inputName"},
1265 @"selectionBase" : @(0),
1266 @"selectionExtent" : @(4),
1267 @"composingBase" : @(-1),
1268 @"composingExtent" : @(-1),
1274 [plugin setMarkedText:@"~"
1275 selectedRange:NSMakeRange(1, 0)
1276 replacementRange:NSMakeRange(NSNotFound, 0)];
1278 NSDictionary* deltaToFramework = @{
1279 @"oldText" :
@"Text",
1280 @"deltaText" :
@"~",
1281 @"deltaStart" : @(0),
1283 @"selectionBase" : @(1),
1284 @"selectionExtent" : @(1),
1285 @"selectionAffinity" :
@"TextAffinity.upstream",
1286 @"selectionIsDirectional" : @(
false),
1287 @"composingBase" : @(0),
1288 @"composingExtent" : @(1),
1290 NSDictionary* expectedState = @{
1291 @"deltas" : @[ deltaToFramework ],
1301 [binaryMessengerMock sendOnChannel:
@"flutter/textinput" message:updateCall]);
1308 - (bool)testPerformKeyEquivalent {
1309 __block NSEvent* eventBeingDispatchedByKeyboardManager = nil;
1311 OCMStub([viewControllerMock isDispatchingKeyEvent:[OCMArg any]])
1312 .andDo(^(NSInvocation* invocation) {
1314 [invocation getArgument:(void*)&event atIndex:2];
1315 BOOL result =
event == eventBeingDispatchedByKeyboardManager;
1316 [invocation setReturnValue:&result];
1319 NSEvent*
event = [NSEvent keyEventWithType:NSEventTypeKeyDown
1320 location:NSZeroPoint
1326 charactersIgnoringModifiers:@""
1333 OCMExpect([viewControllerMock keyDown:event]);
1336 if (![plugin performKeyEquivalent:event]) {
1342 [viewControllerMock keyDown:event]);
1349 eventBeingDispatchedByKeyboardManager = event;
1351 OCMReject([viewControllerMock keyDown:event]);
1355 if ([plugin performKeyEquivalent:event]) {
1365 - (bool)handleArrowKeyWhenImePopoverIsActive {
1369 [engineMock binaryMessenger])
1370 .andReturn(binaryMessengerMock);
1371 OCMStub([[engineMock ignoringNonObjectArgs] sendKeyEvent:FlutterKeyEvent {}
1375 NSTextInputContext* textInputContext = OCMClassMock([NSTextInputContext
class]);
1376 OCMStub([textInputContext handleEvent:[OCMArg any]]).andReturn(YES);
1387 NSDictionary* setClientConfig = @{
1388 @"inputAction" :
@"action",
1389 @"enableDeltaModel" :
@"true",
1390 @"inputType" : @{
@"name" :
@"inputName"},
1403 [plugin setMarkedText:@"m"
1404 selectedRange:NSMakeRange(0, 1)
1405 replacementRange:NSMakeRange(NSNotFound, 0)];
1408 NSEvent*
event = [NSEvent keyEventWithType:NSEventTypeKeyDown
1409 location:NSZeroPoint
1410 modifierFlags:0xa00100
1414 characters:@"\uF702"
1415 charactersIgnoringModifiers:@"\uF702"
1420 [plugin performKeyEquivalent:event];
1422 if ([plugin handleKeyEvent:event] !=
true) {
1427 event = [NSEvent keyEventWithType:NSEventTypeKeyDown
1428 location:NSZeroPoint
1429 modifierFlags:0x40101
1433 characters:@"\uF702"
1434 charactersIgnoringModifiers:@"\uF702"
1439 [plugin performKeyEquivalent:event];
1441 if ([plugin handleKeyEvent:event] !=
false) {
1448 - (bool)unhandledKeyEquivalent {
1452 [engineMock binaryMessenger])
1453 .andReturn(binaryMessengerMock);
1462 NSDictionary* setClientConfig = @{
1463 @"inputAction" :
@"action",
1464 @"enableDeltaModel" :
@"true",
1465 @"inputType" : @{
@"name" :
@"inputName"},
1478 NSEvent*
event = [NSEvent keyEventWithType:NSEventTypeKeyDown
1479 location:NSZeroPoint
1480 modifierFlags:0x40101
1485 charactersIgnoringModifiers:@"h"
1490 [plugin performKeyEquivalent:event];
1494 if ([plugin handleKeyEvent:event] !=
true) {
1499 event = [NSEvent keyEventWithType:NSEventTypeKeyDown
1500 location:NSZeroPoint
1501 modifierFlags:0x100108
1506 charactersIgnoringModifiers:@"w"
1511 [plugin performKeyEquivalent:event];
1515 if ([plugin handleKeyEvent:event] !=
false) {
1522 - (bool)testInsertNewLine {
1526 [engineMock binaryMessenger])
1527 .andReturn(binaryMessengerMock);
1528 OCMStub([[engineMock ignoringNonObjectArgs] sendKeyEvent:FlutterKeyEvent {}
1539 NSDictionary* setClientConfig = @{
1540 @"inputType" : @{
@"name" :
@"TextInputType.multiline"},
1541 @"inputAction" :
@"TextInputAction.newline",
1551 @"selectionBase" : @(4),
1552 @"selectionExtent" : @(4),
1553 @"composingBase" : @(-1),
1554 @"composingExtent" : @(-1),
1563 EXPECT_STREQ([editingState[
@"text"] UTF8String],
"Text");
1564 EXPECT_STREQ([editingState[
@"selectionAffinity"] UTF8String],
"TextAffinity.upstream");
1565 EXPECT_FALSE([editingState[
@"selectionIsDirectional"] boolValue]);
1566 EXPECT_EQ([editingState[
@"selectionBase"] intValue], 4);
1567 EXPECT_EQ([editingState[
@"selectionExtent"] intValue], 4);
1568 EXPECT_EQ([editingState[
@"composingBase"] intValue], -1);
1569 EXPECT_EQ([editingState[
@"composingExtent"] intValue], -1);
1571 [plugin doCommandBySelector:@selector(insertNewline:)];
1575 EXPECT_STREQ([editingState[
@"text"] UTF8String],
"Text\n");
1576 EXPECT_STREQ([editingState[
@"selectionAffinity"] UTF8String],
"TextAffinity.upstream");
1577 EXPECT_FALSE([editingState[
@"selectionIsDirectional"] boolValue]);
1578 EXPECT_EQ([editingState[
@"selectionBase"] intValue], 5);
1579 EXPECT_EQ([editingState[
@"selectionExtent"] intValue], 5);
1580 EXPECT_EQ([editingState[
@"composingBase"] intValue], -1);
1581 EXPECT_EQ([editingState[
@"composingExtent"] intValue], -1);
1586 - (bool)testSendActionDoNotInsertNewLine {
1590 [engineMock binaryMessenger])
1591 .andReturn(binaryMessengerMock);
1592 OCMStub([[engineMock ignoringNonObjectArgs] sendKeyEvent:FlutterKeyEvent {}
1603 NSDictionary* setClientConfig = @{
1604 @"inputType" : @{
@"name" :
@"TextInputType.multiline"},
1605 @"inputAction" :
@"TextInputAction.send",
1615 @"selectionBase" : @(4),
1616 @"selectionExtent" : @(4),
1617 @"composingBase" : @(-1),
1618 @"composingExtent" : @(-1),
1621 NSDictionary* expectedState = @{
1622 @"selectionBase" : @(4),
1623 @"selectionExtent" : @(4),
1624 @"selectionAffinity" :
@"TextAffinity.upstream",
1625 @"selectionIsDirectional" : @(NO),
1626 @"composingBase" : @(-1),
1627 @"composingExtent" : @(-1),
1637 [binaryMessengerMock sendOnChannel:
@"flutter/textinput" message:updateCall]);
1643 [plugin doCommandBySelector:@selector(insertNewline:)];
1648 arguments:@[ @(1), @"TextInputAction.send" ]]];
1653 [binaryMessengerMock sendOnChannel:
@"flutter/textinput" message:performActionCall]);
1658 NSDictionary* updatedState = @{
1659 @"selectionBase" : @(5),
1660 @"selectionExtent" : @(5),
1661 @"selectionAffinity" :
@"TextAffinity.upstream",
1662 @"selectionIsDirectional" : @(NO),
1663 @"composingBase" : @(-1),
1664 @"composingExtent" : @(-1),
1665 @"text" :
@"Text\n",
1676 [binaryMessengerMock sendOnChannel:
@"flutter/textinput" message:updateCall]);
1685 - (bool)testLocalTextAndSelectionUpdateAfterDelta {
1689 [engineMock binaryMessenger])
1690 .andReturn(binaryMessengerMock);
1699 NSDictionary* setClientConfig = @{
1700 @"inputAction" :
@"action",
1701 @"enableDeltaModel" :
@"true",
1702 @"inputType" : @{
@"name" :
@"inputName"},
1708 [plugin insertText:@"text to insert"];
1710 NSDictionary* deltaToFramework = @{
1712 @"deltaText" :
@"text to insert",
1713 @"deltaStart" : @(0),
1715 @"selectionBase" : @(14),
1716 @"selectionExtent" : @(14),
1717 @"selectionAffinity" :
@"TextAffinity.upstream",
1718 @"selectionIsDirectional" : @(
false),
1719 @"composingBase" : @(-1),
1720 @"composingExtent" : @(-1),
1722 NSDictionary* expectedState = @{
1723 @"deltas" : @[ deltaToFramework ],
1733 [binaryMessengerMock sendOnChannel:
@"flutter/textinput" message:updateCall]);
1738 bool localTextAndSelectionUpdated = [plugin.string isEqualToString:@"text to insert"] &&
1739 NSEqualRanges(plugin.selectedRange, NSMakeRange(14, 0));
1741 return localTextAndSelectionUpdated;
1744 - (bool)testSelectorsAreForwardedToFramework {
1748 [engineMock binaryMessenger])
1749 .andReturn(binaryMessengerMock);
1758 NSDictionary* setClientConfig = @{
1759 @"inputAction" :
@"action",
1760 @"enableDeltaModel" :
@"true",
1761 @"inputType" : @{
@"name" :
@"inputName"},
1770 NSString* runLoopMode =
@"FlutterTestRunLoopMode";
1774 [plugin doCommandBySelector:@selector(moveUp:)];
1775 [plugin doCommandBySelector:@selector(moveRightAndModifySelection:)];
1777 __block
bool done =
false;
1778 CFRunLoopPerformBlock(CFRunLoopGetMain(), (__bridge CFStringRef)runLoopMode, ^{
1784 CFRunLoopRunInMode((__bridge CFStringRef)runLoopMode, 0,
true);
1791 @(1), @[ @"moveUp:", @"moveRightAndModifySelection:" ]
1796 [binaryMessengerMock sendOnChannel:
@"flutter/textinput" message:performSelectorCall]);
1804 - (bool)testSelectorsNotForwardedToFrameworkIfNoClient {
1808 [engineMock binaryMessenger])
1809 .andReturn(binaryMessengerMock);
1811 OCMReject([binaryMessengerMock sendOnChannel:
@"flutter/textinput" message:[OCMArg any]]);
1821 NSString* runLoopMode =
@"FlutterTestRunLoopMode";
1825 [plugin doCommandBySelector:@selector(moveUp:)];
1826 [plugin doCommandBySelector:@selector(moveRightAndModifySelection:)];
1828 __block
bool done =
false;
1829 CFRunLoopPerformBlock(CFRunLoopGetMain(), (__bridge CFStringRef)runLoopMode, ^{
1834 CFRunLoopRunInMode((__bridge CFStringRef)runLoopMode, 0,
true);
1847 NSString* fixtures = @(testing::GetFixturesPath());
1849 initWithAssetsPath:fixtures
1850 ICUDataPath:[fixtures stringByAppendingString:@"/icudtl.dat"]];
1851 return [[
FlutterEngine alloc] initWithName:@"test" project:project allowHeadlessExecution:true];
1855 TEST(FlutterTextInputPluginTest, TestEmptyCompositionRange) {
1859 TEST(FlutterTextInputPluginTest, TestSetMarkedTextWithSelectionChange) {
1863 TEST(FlutterTextInputPluginTest, TestSetMarkedTextWithReplacementRange) {
1867 TEST(FlutterTextInputPluginTest, TestComposingRegionRemovedByFramework) {
1871 TEST(FlutterTextInputPluginTest, TestClearClientDuringComposing) {
1875 TEST(FlutterTextInputPluginTest, TestAutocompleteDisabledWhenAutofillNotSet) {
1879 TEST(FlutterTextInputPluginTest, TestAutocompleteEnabledWhenAutofillSet) {
1883 TEST(FlutterTextInputPluginTest, TestAutocompleteEnabledWhenAutofillSetNoHint) {
1887 TEST(FlutterTextInputPluginTest, TestAutocompleteDisabledWhenObscureTextSet) {
1891 TEST(FlutterTextInputPluginTest, TestAutocompleteDisabledWhenPasswordAutofillSet) {
1895 TEST(FlutterTextInputPluginTest, TestAutocompleteDisabledWhenAutofillGroupIncludesPassword) {
1897 testAutocompleteDisabledWhenAutofillGroupIncludesPassword]);
1900 TEST(FlutterTextInputPluginTest, TestFirstRectForCharacterRange) {
1904 TEST(FlutterTextInputPluginTest, TestFirstRectForCharacterRangeAtInfinity) {
1908 TEST(FlutterTextInputPluginTest, TestFirstRectForCharacterRangeWithEsotericAffineTransform) {
1910 testFirstRectForCharacterRangeWithEsotericAffineTransform]);
1913 TEST(FlutterTextInputPluginTest, TestSetEditingStateWithTextEditingDelta) {
1917 TEST(FlutterTextInputPluginTest, TestOperationsThatTriggerDelta) {
1921 TEST(FlutterTextInputPluginTest, TestComposingWithDelta) {
1925 TEST(FlutterTextInputPluginTest, TestComposingWithDeltasWhenSelectionIsActive) {
1929 TEST(FlutterTextInputPluginTest, TestLocalTextAndSelectionUpdateAfterDelta) {
1933 TEST(FlutterTextInputPluginTest, TestPerformKeyEquivalent) {
1937 TEST(FlutterTextInputPluginTest, HandleArrowKeyWhenImePopoverIsActive) {
1941 TEST(FlutterTextInputPluginTest, UnhandledKeyEquivalent) {
1945 TEST(FlutterTextInputPluginTest, TestSelectorsAreForwardedToFramework) {
1949 TEST(FlutterTextInputPluginTest, TestSelectorsNotForwardedToFrameworkIfNoClient) {
1953 TEST(FlutterTextInputPluginTest, TestInsertNewLine) {
1957 TEST(FlutterTextInputPluginTest, TestSendActionDoNotInsertNewLine) {
1961 TEST(FlutterTextInputPluginTest, TestAttributedSubstringOutOfRange) {
1965 [engineMock binaryMessenger])
1966 .andReturn(binaryMessengerMock);
1975 NSDictionary* setClientConfig = @{
1976 @"inputAction" :
@"action",
1977 @"enableDeltaModel" :
@"true",
1978 @"inputType" : @{
@"name" :
@"inputName"},
1988 @"selectionBase" : @(0),
1989 @"selectionExtent" : @(0),
1990 @"composingBase" : @(-1),
1991 @"composingExtent" : @(-1),
1999 NSAttributedString* text = [plugin attributedSubstringForProposedRange:NSMakeRange(1, 10)
2001 EXPECT_TRUE([text.string isEqualToString:
@"ext"]);
2002 EXPECT_EQ(out.location, 1u);
2003 EXPECT_EQ(out.length, 3u);
2005 text = [plugin attributedSubstringForProposedRange:NSMakeRange(4, 10) actualRange:&out];
2006 EXPECT_EQ(text, nil);
2009 TEST(FlutterTextInputPluginTest, CanWorkWithFlutterTextField) {
2014 [viewController loadView];
2016 NSWindow* window = [[NSWindow alloc] initWithContentRect:NSMakeRect(0, 0, 800, 600)
2017 styleMask:NSBorderlessWindowMask
2018 backing:NSBackingStoreBuffered
2020 window.contentView = viewController.view;
2022 engine.semanticsEnabled = YES;
2024 auto bridge = viewController.accessibilityBridge.lock();
2027 ui::AXNode ax_node(&tree,
nullptr, 0, 0);
2028 ui::AXNodeData node_data;
2029 node_data.SetValue(
"initial text");
2030 ax_node.SetData(node_data);
2031 delegate.
Init(viewController.accessibilityBridge, &ax_node);
2037 fieldEditor:viewController.textInputPlugin];
2038 [viewController.view addSubview:mockTextField];
2041 NSDictionary* setClientConfig = @{
2042 @"inputAction" :
@"action",
2043 @"inputType" : @{
@"name" :
@"inputName"},
2050 [viewController.textInputPlugin handleMethodCall:methodCall result:result];
2052 NSDictionary* arguments = @{
2053 @"text" :
@"new text",
2054 @"selectionBase" : @(1),
2055 @"selectionExtent" : @(2),
2056 @"composingBase" : @(-1),
2057 @"composingExtent" : @(-1),
2061 [viewController.textInputPlugin handleMethodCall:methodCall result:result];
2067 [mockTextField setPlatformNode:nil];
2071 [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
2074 TEST(FlutterTextInputPluginTest, CanNotBecomeResponderIfNoViewController) {
2079 [viewController loadView];
2081 NSWindow* window = [[NSWindow alloc] initWithContentRect:NSMakeRect(0, 0, 800, 600)
2082 styleMask:NSBorderlessWindowMask
2083 backing:NSBackingStoreBuffered
2085 window.contentView = viewController.view;
2087 engine.semanticsEnabled = YES;
2089 auto bridge = viewController.accessibilityBridge.lock();
2092 ui::AXNode ax_node(&tree,
nullptr, 0, 0);
2093 ui::AXNodeData node_data;
2094 node_data.SetValue(
"initial text");
2095 ax_node.SetData(node_data);
2096 delegate.
Init(viewController.accessibilityBridge, &ax_node);
2100 EXPECT_EQ([textField becomeFirstResponder], YES);
2102 [engine setViewController:nil];
2105 EXPECT_EQ([textField becomeFirstResponder], NO);
2108 TEST(FlutterTextInputPluginTest, IsAddedAndRemovedFromViewHierarchy) {
2113 [viewController loadView];
2115 NSWindow* window = [[NSWindow alloc] initWithContentRect:NSMakeRect(0, 0, 800, 600)
2116 styleMask:NSBorderlessWindowMask
2117 backing:NSBackingStoreBuffered
2119 window.contentView = viewController.view;
2121 ASSERT_EQ(viewController.textInputPlugin.superview, nil);
2122 ASSERT_FALSE(window.firstResponder == viewController.textInputPlugin);
2124 [viewController.textInputPlugin
2129 ASSERT_EQ(viewController.textInputPlugin.superview, viewController.view);
2130 ASSERT_TRUE(window.firstResponder == viewController.textInputPlugin);
2132 [viewController.textInputPlugin
2137 ASSERT_EQ(viewController.textInputPlugin.superview, nil);
2138 ASSERT_FALSE(window.firstResponder == viewController.textInputPlugin);
2141 TEST(FlutterTextInputPluginTest, FirstResponderIsCorrect) {
2146 [viewController loadView];
2148 NSWindow* window = [[NSWindow alloc] initWithContentRect:NSMakeRect(0, 0, 800, 600)
2149 styleMask:NSBorderlessWindowMask
2150 backing:NSBackingStoreBuffered
2152 window.contentView = viewController.view;
2154 ASSERT_TRUE(viewController.flutterView.acceptsFirstResponder);
2156 [window makeFirstResponder:viewController.flutterView];
2158 [viewController.textInputPlugin
2163 ASSERT_TRUE(window.firstResponder == viewController.textInputPlugin);
2165 ASSERT_FALSE(viewController.flutterView.acceptsFirstResponder);
2167 [viewController.textInputPlugin
2172 ASSERT_TRUE(viewController.flutterView.acceptsFirstResponder);
2173 ASSERT_TRUE(window.firstResponder == viewController.flutterView);
2176 TEST(FlutterTextInputPluginTest, HasZeroSizeAndClipsToBounds) {
2180 [engineMock binaryMessenger])
2181 .andReturn(binaryMessengerMock);
2190 ASSERT_TRUE(NSIsEmptyRect(plugin.frame));
2191 ASSERT_TRUE(plugin.clipsToBounds);