14 #import <OCMock/OCMock.h>
15 #import "flutter/testing/testing.h"
18 #include "flutter/common/constants.h"
21 - (void)setPlatformNode:(
flutter::FlutterTextPlatformNode*)node;
26 @property(nonatomic, nullable, copy) NSString* lastUpdatedString;
27 @property(nonatomic) NSRange lastUpdatedSelection;
33 - (void)updateString:(NSString*)string withSelection:(NSRange)selection {
34 _lastUpdatedString = string;
35 _lastUpdatedSelection = selection;
49 - (nonnull
FlutterView*)createFlutterViewWithMTLDevice:(id<MTLDevice>)device
50 commandQueue:(id<MTLCommandQueue>)commandQueue {
51 return OCMClassMock([NSView
class]);
96 if (viewIdentifier ==
kViewId) {
98 }
else if (viewIdentifier == flutter::kFlutterImplicitViewId) {
113 [engineMock binaryMessenger])
114 .andReturn(binaryMessengerMock);
122 viewController:viewController];
126 NSDictionary* setClientConfig = @{
128 @"inputAction" :
@"action",
129 @"inputType" : @{
@"name" :
@"inputName"},
139 @"selectionBase" : @(0),
140 @"selectionExtent" : @(0),
141 @"composingBase" : @(-1),
142 @"composingExtent" : @(-1),
151 EXPECT_STREQ([editingState[
@"text"] UTF8String],
"Text");
152 EXPECT_STREQ([editingState[
@"selectionAffinity"] UTF8String],
"TextAffinity.upstream");
153 EXPECT_FALSE([editingState[
@"selectionIsDirectional"] boolValue]);
154 EXPECT_EQ([editingState[
@"selectionBase"] intValue], 0);
155 EXPECT_EQ([editingState[
@"selectionExtent"] intValue], 0);
156 EXPECT_EQ([editingState[
@"composingBase"] intValue], -1);
157 EXPECT_EQ([editingState[
@"composingExtent"] intValue], -1);
161 - (bool)testSetMarkedTextWithSelectionChange {
165 [engineMock binaryMessenger])
166 .andReturn(binaryMessengerMock);
174 viewController:viewController];
178 NSDictionary* setClientConfig = @{
180 @"inputAction" :
@"action",
181 @"inputType" : @{
@"name" :
@"inputName"},
191 @"selectionBase" : @(4),
192 @"selectionExtent" : @(4),
193 @"composingBase" : @(-1),
194 @"composingExtent" : @(-1),
200 [plugin setMarkedText:@"marked"
201 selectedRange:NSMakeRange(1, 0)
202 replacementRange:NSMakeRange(NSNotFound, 0)];
204 NSDictionary* expectedState = @{
205 @"selectionBase" : @(5),
206 @"selectionExtent" : @(5),
207 @"selectionAffinity" :
@"TextAffinity.upstream",
208 @"selectionIsDirectional" : @(NO),
209 @"composingBase" : @(4),
210 @"composingExtent" : @(10),
211 @"text" :
@"Textmarked",
220 [binaryMessengerMock sendOnChannel:
@"flutter/textinput" message:updateCall]);
224 [binaryMessengerMock sendOnChannel:
@"flutter/textinput" message:updateCall]);
231 - (bool)testSetMarkedTextWithReplacementRange {
235 [engineMock binaryMessenger])
236 .andReturn(binaryMessengerMock);
244 viewController:viewController];
248 NSDictionary* setClientConfig = @{
250 @"inputAction" :
@"action",
251 @"inputType" : @{
@"name" :
@"inputName"},
261 @"selectionBase" : @(3),
262 @"selectionExtent" : @(3),
263 @"composingBase" : @(-1),
264 @"composingExtent" : @(-1),
270 [plugin setMarkedText:@"marked"
271 selectedRange:NSMakeRange(1, 0)
272 replacementRange:NSMakeRange(1, 2)];
274 NSDictionary* expectedState = @{
275 @"selectionBase" : @(2),
276 @"selectionExtent" : @(2),
277 @"selectionAffinity" :
@"TextAffinity.upstream",
278 @"selectionIsDirectional" : @(NO),
279 @"composingBase" : @(1),
280 @"composingExtent" : @(7),
281 @"text" :
@"1marked4",
290 [binaryMessengerMock sendOnChannel:
@"flutter/textinput" message:updateCall]);
294 [binaryMessengerMock sendOnChannel:
@"flutter/textinput" message:updateCall]);
301 - (bool)testComposingRegionRemovedByFramework {
305 [engineMock binaryMessenger])
306 .andReturn(binaryMessengerMock);
314 viewController:viewController];
318 NSDictionary* setClientConfig = @{
320 @"inputAction" :
@"action",
321 @"inputType" : @{
@"name" :
@"inputName"},
331 @"selectionBase" : @(4),
332 @"selectionExtent" : @(4),
333 @"composingBase" : @(2),
334 @"composingExtent" : @(4),
344 @"selectionBase" : @(2),
345 @"selectionExtent" : @(2),
346 @"composingBase" : @(-1),
347 @"composingExtent" : @(-1),
355 EXPECT_STREQ([editingState[
@"text"] UTF8String],
"Te");
356 EXPECT_STREQ([editingState[
@"selectionAffinity"] UTF8String],
"TextAffinity.upstream");
357 EXPECT_FALSE([editingState[
@"selectionIsDirectional"] boolValue]);
358 EXPECT_EQ([editingState[
@"selectionBase"] intValue], 2);
359 EXPECT_EQ([editingState[
@"selectionExtent"] intValue], 2);
360 EXPECT_EQ([editingState[
@"composingBase"] intValue], -1);
361 EXPECT_EQ([editingState[
@"composingExtent"] intValue], -1);
370 [engineMock binaryMessenger])
371 .andReturn(binaryMessengerMock);
377 viewController:viewController];
382 NSDictionary* setClientConfig = @{
384 @"inputAction" :
@"action",
385 @"inputType" : @{
@"name" :
@"inputName"},
396 @"selectionBase" : @(0),
397 @"selectionExtent" : @(0),
398 @"composingBase" : @(0),
399 @"composingExtent" : @(1),
406 EXPECT_EQ([editingState[
@"composingBase"] intValue], 0);
407 EXPECT_EQ([editingState[
@"composingExtent"] intValue], 1);
417 EXPECT_EQ([editingState[
@"composingBase"] intValue], [editingState[
@"composingExtent"] intValue]);
421 - (bool)testAutocompleteDisabledWhenAutofillNotSet {
426 [engineMock binaryMessenger])
427 .andReturn(binaryMessengerMock);
433 viewController:viewController];
438 NSDictionary* setClientConfig = @{
440 @"inputAction" :
@"action",
441 @"inputType" : @{
@"name" :
@"inputName"},
449 EXPECT_FALSE([plugin isAutomaticTextCompletionEnabled]);
453 - (bool)testAutocompleteEnabledWhenAutofillSet {
458 [engineMock binaryMessenger])
459 .andReturn(binaryMessengerMock);
465 viewController:viewController];
470 NSDictionary* setClientConfig = @{
472 @"inputAction" :
@"action",
473 @"inputType" : @{
@"name" :
@"inputName"},
475 @"uniqueIdentifier" :
@"field1",
476 @"hints" : @[ @"name" ],
477 @"editingValue" : @{
@"text" :
@""},
486 EXPECT_TRUE([plugin isAutomaticTextCompletionEnabled]);
489 if (@available(macOS 11.0, *)) {
490 EXPECT_EQ([plugin contentType], nil);
495 - (bool)testAutocompleteEnabledWhenAutofillSetNoHint {
500 [engineMock binaryMessenger])
501 .andReturn(binaryMessengerMock);
507 viewController:viewController];
512 NSDictionary* setClientConfig = @{
514 @"inputAction" :
@"action",
515 @"inputType" : @{
@"name" :
@"inputName"},
517 @"uniqueIdentifier" :
@"field1",
519 @"editingValue" : @{
@"text" :
@""},
528 EXPECT_TRUE([plugin isAutomaticTextCompletionEnabled]);
532 - (bool)testAutocompleteDisabledWhenObscureTextSet {
537 [engineMock binaryMessenger])
538 .andReturn(binaryMessengerMock);
544 viewController:viewController];
549 NSDictionary* setClientConfig = @{
551 @"inputAction" :
@"action",
552 @"inputType" : @{
@"name" :
@"inputName"},
553 @"obscureText" : @YES,
555 @"uniqueIdentifier" :
@"field1",
556 @"editingValue" : @{
@"text" :
@""},
565 EXPECT_FALSE([plugin isAutomaticTextCompletionEnabled]);
569 - (bool)testAutocompleteDisabledWhenPasswordAutofillSet {
574 [engineMock binaryMessenger])
575 .andReturn(binaryMessengerMock);
581 viewController:viewController];
586 NSDictionary* setClientConfig = @{
588 @"inputAction" :
@"action",
589 @"inputType" : @{
@"name" :
@"inputName"},
591 @"uniqueIdentifier" :
@"field1",
592 @"hints" : @[ @"password" ],
593 @"editingValue" : @{
@"text" :
@""},
602 EXPECT_FALSE([plugin isAutomaticTextCompletionEnabled]);
605 if (@available(macOS 11.0, *)) {
606 EXPECT_EQ([plugin contentType], NSTextContentTypePassword);
611 - (bool)testAutocompleteDisabledWhenAutofillGroupIncludesPassword {
616 [engineMock binaryMessenger])
617 .andReturn(binaryMessengerMock);
623 viewController:viewController];
628 NSDictionary* setClientConfig = @{
630 @"inputAction" :
@"action",
631 @"inputType" : @{
@"name" :
@"inputName"},
634 @"inputAction" : @"action",
635 @"inputType" : @{@"name" : @"inputName"},
637 @"uniqueIdentifier" : @"field1",
638 @"hints" : @[ @"password" ],
639 @"editingValue" : @{@"text" : @""},
643 @"inputAction" : @"action",
644 @"inputType" : @{@"name" : @"inputName"},
646 @"uniqueIdentifier" : @"field2",
647 @"hints" : @[ @"name" ],
648 @"editingValue" : @{@"text" : @""},
659 EXPECT_FALSE([plugin isAutomaticTextCompletionEnabled]);
663 - (bool)testContentTypeWhenAutofillTypeIsUsername {
668 [engineMock binaryMessenger])
669 .andReturn(binaryMessengerMock);
675 viewController:viewController];
680 NSDictionary* setClientConfig = @{
682 @"inputAction" :
@"action",
683 @"inputType" : @{
@"name" :
@"inputName"},
685 @"uniqueIdentifier" :
@"field1",
686 @"hints" : @[ @"name" ],
687 @"editingValue" : @{
@"text" :
@""},
696 EXPECT_FALSE([plugin isAutomaticTextCompletionEnabled]);
699 if (@available(macOS 11.0, *)) {
700 EXPECT_EQ([plugin contentType], NSTextContentTypeUsername);
705 - (bool)testContentTypeWhenAutofillTypeIsOneTimeCode {
710 [engineMock binaryMessenger])
711 .andReturn(binaryMessengerMock);
717 viewController:viewController];
722 NSDictionary* setClientConfig = @{
724 @"inputAction" :
@"action",
725 @"inputType" : @{
@"name" :
@"inputName"},
727 @"uniqueIdentifier" :
@"field1",
728 @"hints" : @[ @"oneTimeCode" ],
729 @"editingValue" : @{
@"text" :
@""},
738 EXPECT_FALSE([plugin isAutomaticTextCompletionEnabled]);
741 if (@available(macOS 11.0, *)) {
742 EXPECT_EQ([plugin contentType], NSTextContentTypeOneTimeCode);
747 - (bool)testFirstRectForCharacterRange {
751 [engineMock binaryMessenger])
752 .andReturn(binaryMessengerMock);
755 [controllerMock loadView];
756 id viewMock = controllerMock.flutterView;
759 .andReturn(NSMakeRect(0, 0, 200, 200));
761 id windowMock = OCMClassMock([NSWindow
class]);
764 .andReturn(windowMock);
767 [viewMock convertRect:NSMakeRect(28, 10, 2, 19) toView:nil])
768 .andReturn(NSMakeRect(28, 10, 2, 19));
771 [windowMock convertRectToScreen:NSMakeRect(28, 10, 2, 19)])
772 .andReturn(NSMakeRect(38, 20, 2, 19));
776 viewController:controllerMock];
780 NSDictionary* setClientConfig = @{
793 @(1.0), @(0.0), @(0.0), @(0.0), @(0.0), @(1.0), @(0.0), @(0.0), @(0.0),
794 @(0.0), @(1.0), @(0.0), @(20.0), @(10.0), @(0.0), @(1.0)
818 [windowMock convertRectToScreen:NSMakeRect(28, 10, 2, 19)]);
823 return NSEqualRects(rect, NSMakeRect(38, 20, 2, 19));
826 - (bool)testFirstRectForCharacterRangeAtInfinity {
830 [engineMock binaryMessenger])
831 .andReturn(binaryMessengerMock);
834 [controllerMock loadView];
835 id viewMock = controllerMock.flutterView;
838 .andReturn(NSMakeRect(0, 0, 200, 200));
840 id windowMock = OCMClassMock([NSWindow
class]);
843 .andReturn(windowMock);
847 viewController:controllerMock];
851 NSDictionary* setClientConfig = @{
865 @(1.0), @(0.0), @(0.0), @(0.0), @(0.0), @(1.0), @(0.0), @(0.0), @(0.0),
866 @(0.0), @(1.0), @(0.0), @(20.0), @(10.0), @(0.0), @(0.0)
888 return NSEqualRects(rect, CGRectZero);
891 - (bool)testFirstRectForCharacterRangeWithEsotericAffineTransform {
895 [engineMock binaryMessenger])
896 .andReturn(binaryMessengerMock);
899 [controllerMock loadView];
900 id viewMock = controllerMock.flutterView;
903 .andReturn(NSMakeRect(0, 0, 200, 200));
905 id windowMock = OCMClassMock([NSWindow
class]);
908 .andReturn(windowMock);
911 [viewMock convertRect:NSMakeRect(-18, 6, 3, 3) toView:nil])
912 .andReturn(NSMakeRect(-18, 6, 3, 3));
915 [windowMock convertRectToScreen:NSMakeRect(-18, 6, 3, 3)])
916 .andReturn(NSMakeRect(-18, 6, 3, 3));
920 viewController:controllerMock];
924 NSDictionary* setClientConfig = @{
940 @(0.0), @(3.0), @(0.0), @(0.0), @(-3.0), @(0.0), @(0.0), @(0.0), @(0.0),
941 @(0.0), @(3.0), @(0.0), @(-6.0), @(3.0), @(9.0), @(1.0)
966 [windowMock convertRectToScreen:NSMakeRect(-18, 6, 3, 3)]);
971 return NSEqualRects(rect, NSMakeRect(-18, 6, 3, 3));
974 - (bool)testSetEditingStateWithTextEditingDelta {
978 [engineMock binaryMessenger])
979 .andReturn(binaryMessengerMock);
987 viewController:viewController];
991 NSDictionary* setClientConfig = @{
993 @"inputAction" :
@"action",
994 @"enableDeltaModel" :
@"true",
995 @"inputType" : @{
@"name" :
@"inputName"},
1005 @"selectionBase" : @(0),
1006 @"selectionExtent" : @(0),
1007 @"composingBase" : @(-1),
1008 @"composingExtent" : @(-1),
1017 EXPECT_STREQ([editingState[
@"text"] UTF8String],
"Text");
1018 EXPECT_STREQ([editingState[
@"selectionAffinity"] UTF8String],
"TextAffinity.upstream");
1019 EXPECT_FALSE([editingState[
@"selectionIsDirectional"] boolValue]);
1020 EXPECT_EQ([editingState[
@"selectionBase"] intValue], 0);
1021 EXPECT_EQ([editingState[
@"selectionExtent"] intValue], 0);
1022 EXPECT_EQ([editingState[
@"composingBase"] intValue], -1);
1023 EXPECT_EQ([editingState[
@"composingExtent"] intValue], -1);
1027 - (bool)testOperationsThatTriggerDelta {
1031 [engineMock binaryMessenger])
1032 .andReturn(binaryMessengerMock);
1040 viewController:viewController];
1044 NSDictionary* setClientConfig = @{
1046 @"inputAction" :
@"action",
1047 @"enableDeltaModel" :
@"true",
1048 @"inputType" : @{
@"name" :
@"inputName"},
1054 [plugin insertText:@"text to insert"];
1056 NSDictionary* deltaToFramework = @{
1058 @"deltaText" :
@"text to insert",
1059 @"deltaStart" : @(0),
1061 @"selectionBase" : @(14),
1062 @"selectionExtent" : @(14),
1063 @"selectionAffinity" :
@"TextAffinity.upstream",
1064 @"selectionIsDirectional" : @(
false),
1065 @"composingBase" : @(-1),
1066 @"composingExtent" : @(-1),
1068 NSDictionary* expectedState = @{
1069 @"deltas" : @[ deltaToFramework ],
1079 [binaryMessengerMock sendOnChannel:
@"flutter/textinput" message:updateCall]);
1084 [plugin setMarkedText:@"marked text" selectedRange:NSMakeRange(0, 1)];
1086 deltaToFramework = @{
1087 @"oldText" :
@"text to insert",
1088 @"deltaText" :
@"marked text",
1089 @"deltaStart" : @(14),
1090 @"deltaEnd" : @(14),
1091 @"selectionBase" : @(14),
1092 @"selectionExtent" : @(15),
1093 @"selectionAffinity" :
@"TextAffinity.upstream",
1094 @"selectionIsDirectional" : @(
false),
1095 @"composingBase" : @(14),
1096 @"composingExtent" : @(25),
1099 @"deltas" : @[ deltaToFramework ],
1109 [binaryMessengerMock sendOnChannel:
@"flutter/textinput" message:updateCall]);
1114 [plugin unmarkText];
1116 deltaToFramework = @{
1117 @"oldText" :
@"text to insertmarked text",
1119 @"deltaStart" : @(-1),
1120 @"deltaEnd" : @(-1),
1121 @"selectionBase" : @(25),
1122 @"selectionExtent" : @(25),
1123 @"selectionAffinity" :
@"TextAffinity.upstream",
1124 @"selectionIsDirectional" : @(false),
1125 @"composingBase" : @(-1),
1126 @"composingExtent" : @(-1),
1129 @"deltas" : @[ deltaToFramework ],
1139 [binaryMessengerMock sendOnChannel:
@"flutter/textinput" message:updateCall]);
1146 - (bool)testComposingWithDelta {
1150 [engineMock binaryMessenger])
1151 .andReturn(binaryMessengerMock);
1159 viewController:viewController];
1163 NSDictionary* setClientConfig = @{
1165 @"inputAction" :
@"action",
1166 @"enableDeltaModel" :
@"true",
1167 @"inputType" : @{
@"name" :
@"inputName"},
1173 [plugin setMarkedText:@"m" selectedRange:NSMakeRange(0, 1)];
1175 NSDictionary* deltaToFramework = @{
1177 @"deltaText" :
@"m",
1178 @"deltaStart" : @(0),
1180 @"selectionBase" : @(0),
1181 @"selectionExtent" : @(1),
1182 @"selectionAffinity" :
@"TextAffinity.upstream",
1183 @"selectionIsDirectional" : @(
false),
1184 @"composingBase" : @(0),
1185 @"composingExtent" : @(1),
1187 NSDictionary* expectedState = @{
1188 @"deltas" : @[ deltaToFramework ],
1198 [binaryMessengerMock sendOnChannel:
@"flutter/textinput" message:updateCall]);
1203 [plugin setMarkedText:@"ma" selectedRange:NSMakeRange(0, 1)];
1205 deltaToFramework = @{
1207 @"deltaText" :
@"ma",
1208 @"deltaStart" : @(0),
1210 @"selectionBase" : @(0),
1211 @"selectionExtent" : @(1),
1212 @"selectionAffinity" :
@"TextAffinity.upstream",
1213 @"selectionIsDirectional" : @(
false),
1214 @"composingBase" : @(0),
1215 @"composingExtent" : @(2),
1218 @"deltas" : @[ deltaToFramework ],
1228 [binaryMessengerMock sendOnChannel:
@"flutter/textinput" message:updateCall]);
1233 [plugin setMarkedText:@"mar" selectedRange:NSMakeRange(0, 1)];
1235 deltaToFramework = @{
1237 @"deltaText" :
@"mar",
1238 @"deltaStart" : @(0),
1240 @"selectionBase" : @(0),
1241 @"selectionExtent" : @(1),
1242 @"selectionAffinity" :
@"TextAffinity.upstream",
1243 @"selectionIsDirectional" : @(
false),
1244 @"composingBase" : @(0),
1245 @"composingExtent" : @(3),
1248 @"deltas" : @[ deltaToFramework ],
1258 [binaryMessengerMock sendOnChannel:
@"flutter/textinput" message:updateCall]);
1263 [plugin setMarkedText:@"mark" selectedRange:NSMakeRange(0, 1)];
1265 deltaToFramework = @{
1266 @"oldText" :
@"mar",
1267 @"deltaText" :
@"mark",
1268 @"deltaStart" : @(0),
1270 @"selectionBase" : @(0),
1271 @"selectionExtent" : @(1),
1272 @"selectionAffinity" :
@"TextAffinity.upstream",
1273 @"selectionIsDirectional" : @(
false),
1274 @"composingBase" : @(0),
1275 @"composingExtent" : @(4),
1278 @"deltas" : @[ deltaToFramework ],
1288 [binaryMessengerMock sendOnChannel:
@"flutter/textinput" message:updateCall]);
1293 [plugin setMarkedText:@"marke" selectedRange:NSMakeRange(0, 1)];
1295 deltaToFramework = @{
1296 @"oldText" :
@"mark",
1297 @"deltaText" :
@"marke",
1298 @"deltaStart" : @(0),
1300 @"selectionBase" : @(0),
1301 @"selectionExtent" : @(1),
1302 @"selectionAffinity" :
@"TextAffinity.upstream",
1303 @"selectionIsDirectional" : @(
false),
1304 @"composingBase" : @(0),
1305 @"composingExtent" : @(5),
1308 @"deltas" : @[ deltaToFramework ],
1318 [binaryMessengerMock sendOnChannel:
@"flutter/textinput" message:updateCall]);
1323 [plugin setMarkedText:@"marked" selectedRange:NSMakeRange(0, 1)];
1325 deltaToFramework = @{
1326 @"oldText" :
@"marke",
1327 @"deltaText" :
@"marked",
1328 @"deltaStart" : @(0),
1330 @"selectionBase" : @(0),
1331 @"selectionExtent" : @(1),
1332 @"selectionAffinity" :
@"TextAffinity.upstream",
1333 @"selectionIsDirectional" : @(
false),
1334 @"composingBase" : @(0),
1335 @"composingExtent" : @(6),
1338 @"deltas" : @[ deltaToFramework ],
1348 [binaryMessengerMock sendOnChannel:
@"flutter/textinput" message:updateCall]);
1353 [plugin unmarkText];
1355 deltaToFramework = @{
1356 @"oldText" :
@"marked",
1358 @"deltaStart" : @(-1),
1359 @"deltaEnd" : @(-1),
1360 @"selectionBase" : @(6),
1361 @"selectionExtent" : @(6),
1362 @"selectionAffinity" :
@"TextAffinity.upstream",
1363 @"selectionIsDirectional" : @(false),
1364 @"composingBase" : @(-1),
1365 @"composingExtent" : @(-1),
1368 @"deltas" : @[ deltaToFramework ],
1378 [binaryMessengerMock sendOnChannel:
@"flutter/textinput" message:updateCall]);
1385 - (bool)testComposingWithDeltasWhenSelectionIsActive {
1389 [engineMock binaryMessenger])
1390 .andReturn(binaryMessengerMock);
1398 viewController:viewController];
1402 NSDictionary* setClientConfig = @{
1404 @"inputAction" :
@"action",
1405 @"enableDeltaModel" :
@"true",
1406 @"inputType" : @{
@"name" :
@"inputName"},
1416 @"selectionBase" : @(0),
1417 @"selectionExtent" : @(4),
1418 @"composingBase" : @(-1),
1419 @"composingExtent" : @(-1),
1425 [plugin setMarkedText:@"~"
1426 selectedRange:NSMakeRange(1, 0)
1427 replacementRange:NSMakeRange(NSNotFound, 0)];
1429 NSDictionary* deltaToFramework = @{
1430 @"oldText" :
@"Text",
1431 @"deltaText" :
@"~",
1432 @"deltaStart" : @(0),
1434 @"selectionBase" : @(1),
1435 @"selectionExtent" : @(1),
1436 @"selectionAffinity" :
@"TextAffinity.upstream",
1437 @"selectionIsDirectional" : @(
false),
1438 @"composingBase" : @(0),
1439 @"composingExtent" : @(1),
1441 NSDictionary* expectedState = @{
1442 @"deltas" : @[ deltaToFramework ],
1452 [binaryMessengerMock sendOnChannel:
@"flutter/textinput" message:updateCall]);
1459 - (bool)testPerformKeyEquivalent {
1461 __block NSEvent* eventBeingDispatchedByKeyboardManager = nil;
1463 OCMStub([viewControllerMock isDispatchingKeyEvent:[OCMArg any]])
1464 .andDo(^(NSInvocation* invocation) {
1466 [invocation getArgument:(void*)&event atIndex:2];
1467 BOOL result =
event == eventBeingDispatchedByKeyboardManager;
1468 [invocation setReturnValue:&result];
1471 NSEvent*
event = [NSEvent keyEventWithType:NSEventTypeKeyDown
1472 location:NSZeroPoint
1478 charactersIgnoringModifiers:@""
1484 viewController:viewControllerMock];
1488 NSDictionary* setClientConfig = @{
1496 OCMExpect([viewControllerMock keyDown:event]);
1499 if (![plugin performKeyEquivalent:event]) {
1505 [viewControllerMock keyDown:event]);
1512 eventBeingDispatchedByKeyboardManager = event;
1514 OCMReject([viewControllerMock keyDown:event]);
1518 if ([plugin performKeyEquivalent:event]) {
1528 - (bool)handleArrowKeyWhenImePopoverIsActive {
1532 [engineMock binaryMessenger])
1533 .andReturn(binaryMessengerMock);
1534 OCMStub([[engineMock ignoringNonObjectArgs] sendKeyEvent:FlutterKeyEvent {}
1538 NSTextInputContext* textInputContext = OCMClassMock([NSTextInputContext
class]);
1539 OCMStub([textInputContext handleEvent:[OCMArg any]]).andReturn(YES);
1547 viewController:viewController];
1553 NSDictionary* setClientConfig = @{
1555 @"inputAction" :
@"action",
1556 @"enableDeltaModel" :
@"true",
1557 @"inputType" : @{
@"name" :
@"inputName"},
1570 [plugin setMarkedText:@"m"
1571 selectedRange:NSMakeRange(0, 1)
1572 replacementRange:NSMakeRange(NSNotFound, 0)];
1575 NSEvent*
event = [NSEvent keyEventWithType:NSEventTypeKeyDown
1576 location:NSZeroPoint
1577 modifierFlags:0xa00100
1581 characters:@"\uF702"
1582 charactersIgnoringModifiers:@"\uF702"
1587 [plugin performKeyEquivalent:event];
1589 if ([plugin handleKeyEvent:event] !=
true) {
1594 event = [NSEvent keyEventWithType:NSEventTypeKeyDown
1595 location:NSZeroPoint
1596 modifierFlags:0x40101
1600 characters:@"\uF702"
1601 charactersIgnoringModifiers:@"\uF702"
1606 [plugin performKeyEquivalent:event];
1608 if ([plugin handleKeyEvent:event] !=
false) {
1615 - (bool)unhandledKeyEquivalent {
1619 [engineMock binaryMessenger])
1620 .andReturn(binaryMessengerMock);
1628 viewController:viewController];
1632 NSDictionary* setClientConfig = @{
1634 @"inputAction" :
@"action",
1635 @"enableDeltaModel" :
@"true",
1636 @"inputType" : @{
@"name" :
@"inputName"},
1649 NSEvent*
event = [NSEvent keyEventWithType:NSEventTypeKeyDown
1650 location:NSZeroPoint
1651 modifierFlags:0x40101
1656 charactersIgnoringModifiers:@"h"
1661 [plugin performKeyEquivalent:event];
1665 if ([plugin handleKeyEvent:event] !=
true) {
1670 event = [NSEvent keyEventWithType:NSEventTypeKeyDown
1671 location:NSZeroPoint
1672 modifierFlags:0x100108
1677 charactersIgnoringModifiers:@"w"
1682 [plugin performKeyEquivalent:event];
1686 if ([plugin handleKeyEvent:event] !=
false) {
1693 - (bool)testInsertNewLine {
1697 [engineMock binaryMessenger])
1698 .andReturn(binaryMessengerMock);
1699 OCMStub([[engineMock ignoringNonObjectArgs] sendKeyEvent:FlutterKeyEvent {}
1709 viewController:viewController];
1713 NSDictionary* setClientConfig = @{
1715 @"inputType" : @{
@"name" :
@"TextInputType.multiline"},
1716 @"inputAction" :
@"TextInputAction.newline",
1726 @"selectionBase" : @(4),
1727 @"selectionExtent" : @(4),
1728 @"composingBase" : @(-1),
1729 @"composingExtent" : @(-1),
1738 EXPECT_STREQ([editingState[
@"text"] UTF8String],
"Text");
1739 EXPECT_STREQ([editingState[
@"selectionAffinity"] UTF8String],
"TextAffinity.upstream");
1740 EXPECT_FALSE([editingState[
@"selectionIsDirectional"] boolValue]);
1741 EXPECT_EQ([editingState[
@"selectionBase"] intValue], 4);
1742 EXPECT_EQ([editingState[
@"selectionExtent"] intValue], 4);
1743 EXPECT_EQ([editingState[
@"composingBase"] intValue], -1);
1744 EXPECT_EQ([editingState[
@"composingExtent"] intValue], -1);
1746 [plugin doCommandBySelector:@selector(insertNewline:)];
1750 EXPECT_STREQ([editingState[
@"text"] UTF8String],
"Text\n");
1751 EXPECT_STREQ([editingState[
@"selectionAffinity"] UTF8String],
"TextAffinity.upstream");
1752 EXPECT_FALSE([editingState[
@"selectionIsDirectional"] boolValue]);
1753 EXPECT_EQ([editingState[
@"selectionBase"] intValue], 5);
1754 EXPECT_EQ([editingState[
@"selectionExtent"] intValue], 5);
1755 EXPECT_EQ([editingState[
@"composingBase"] intValue], -1);
1756 EXPECT_EQ([editingState[
@"composingExtent"] intValue], -1);
1761 - (bool)testSendActionDoNotInsertNewLine {
1765 [engineMock binaryMessenger])
1766 .andReturn(binaryMessengerMock);
1767 OCMStub([[engineMock ignoringNonObjectArgs] sendKeyEvent:FlutterKeyEvent {}
1777 viewController:viewController];
1781 NSDictionary* setClientConfig = @{
1783 @"inputType" : @{
@"name" :
@"TextInputType.multiline"},
1784 @"inputAction" :
@"TextInputAction.send",
1794 @"selectionBase" : @(4),
1795 @"selectionExtent" : @(4),
1796 @"composingBase" : @(-1),
1797 @"composingExtent" : @(-1),
1800 NSDictionary* expectedState = @{
1801 @"selectionBase" : @(4),
1802 @"selectionExtent" : @(4),
1803 @"selectionAffinity" :
@"TextAffinity.upstream",
1804 @"selectionIsDirectional" : @(NO),
1805 @"composingBase" : @(-1),
1806 @"composingExtent" : @(-1),
1816 [binaryMessengerMock sendOnChannel:
@"flutter/textinput" message:updateCall]);
1822 [plugin doCommandBySelector:@selector(insertNewline:)];
1827 arguments:@[ @(1), @"TextInputAction.send" ]]];
1832 [binaryMessengerMock sendOnChannel:
@"flutter/textinput" message:performActionCall]);
1837 NSDictionary* updatedState = @{
1838 @"selectionBase" : @(5),
1839 @"selectionExtent" : @(5),
1840 @"selectionAffinity" :
@"TextAffinity.upstream",
1841 @"selectionIsDirectional" : @(NO),
1842 @"composingBase" : @(-1),
1843 @"composingExtent" : @(-1),
1844 @"text" :
@"Text\n",
1855 [binaryMessengerMock sendOnChannel:
@"flutter/textinput" message:updateCall]);
1864 - (bool)testLocalTextAndSelectionUpdateAfterDelta {
1868 [engineMock binaryMessenger])
1869 .andReturn(binaryMessengerMock);
1877 viewController:viewController];
1881 NSDictionary* setClientConfig = @{
1883 @"inputAction" :
@"action",
1884 @"enableDeltaModel" :
@"true",
1885 @"inputType" : @{
@"name" :
@"inputName"},
1891 [plugin insertText:@"text to insert"];
1893 NSDictionary* deltaToFramework = @{
1895 @"deltaText" :
@"text to insert",
1896 @"deltaStart" : @(0),
1898 @"selectionBase" : @(14),
1899 @"selectionExtent" : @(14),
1900 @"selectionAffinity" :
@"TextAffinity.upstream",
1901 @"selectionIsDirectional" : @(
false),
1902 @"composingBase" : @(-1),
1903 @"composingExtent" : @(-1),
1905 NSDictionary* expectedState = @{
1906 @"deltas" : @[ deltaToFramework ],
1916 [binaryMessengerMock sendOnChannel:
@"flutter/textinput" message:updateCall]);
1921 bool localTextAndSelectionUpdated = [plugin.string isEqualToString:@"text to insert"] &&
1922 NSEqualRanges(plugin.selectedRange, NSMakeRange(14, 0));
1924 return localTextAndSelectionUpdated;
1927 - (bool)testSelectorsAreForwardedToFramework {
1931 [engineMock binaryMessenger])
1932 .andReturn(binaryMessengerMock);
1940 viewController:viewController];
1944 NSDictionary* setClientConfig = @{
1946 @"inputAction" :
@"action",
1947 @"enableDeltaModel" :
@"true",
1948 @"inputType" : @{
@"name" :
@"inputName"},
1957 NSString* runLoopMode =
@"FlutterTestRunLoopMode";
1961 [plugin doCommandBySelector:@selector(moveUp:)];
1962 [plugin doCommandBySelector:@selector(moveRightAndModifySelection:)];
1964 __block
bool done =
false;
1965 CFRunLoopPerformBlock(CFRunLoopGetMain(), (__bridge CFStringRef)runLoopMode, ^{
1971 CFRunLoopRunInMode((__bridge CFStringRef)runLoopMode, 0,
true);
1978 @(1), @[ @"moveUp:", @"moveRightAndModifySelection:" ]
1983 [binaryMessengerMock sendOnChannel:
@"flutter/textinput" message:performSelectorCall]);
1991 - (bool)testSelectorsNotForwardedToFrameworkIfNoClient {
1995 [engineMock binaryMessenger])
1996 .andReturn(binaryMessengerMock);
1998 OCMReject([binaryMessengerMock sendOnChannel:
@"flutter/textinput" message:[OCMArg any]]);
2005 viewController:viewController];
2011 NSString* runLoopMode =
@"FlutterTestRunLoopMode";
2015 [plugin doCommandBySelector:@selector(moveUp:)];
2016 [plugin doCommandBySelector:@selector(moveRightAndModifySelection:)];
2018 __block
bool done =
false;
2019 CFRunLoopPerformBlock(CFRunLoopGetMain(), (__bridge CFStringRef)runLoopMode, ^{
2024 CFRunLoopRunInMode((__bridge CFStringRef)runLoopMode, 0,
true);
2030 - (bool)testInsertTextWithCollapsedSelectionInsideComposing {
2033 OCMStub([engineMock binaryMessenger]).andReturn(binaryMessengerMock);
2040 viewController:viewController];
2043 NSDictionary* setClientConfig = @{
2045 @"inputAction" :
@"action",
2046 @"inputType" : @{
@"name" :
@"text"},
2055 @"text" : @"ä »Šæ —¥ã ¯å ®¶ã «å ¸°ã ‚Šã ¾ã ™",
2056 @"selectionBase" : @(0),
2057 @"selectionExtent" : @(3),
2058 @"composingBase" : @(0),
2059 @"composingExtent" : @(9),
2065 [plugin insertText:@"ä »Šæ —¥ã ¯å ®¶ã «å ¸°ã ‚Šã ¾ã ™" replacementRange:NSMakeRange(NSNotFound, 0)];
2068 EXPECT_STREQ([editingState[
@"text"] UTF8String],
"今日ã¯å®¶ã«å¸°ã‚Šã¾ã™");
2069 EXPECT_EQ([editingState[
@"selectionBase"] intValue], 9);
2070 EXPECT_EQ([editingState[
@"selectionExtent"] intValue], 9);
2082 NSString* fixtures = @(testing::GetFixturesPath());
2084 initWithAssetsPath:fixtures
2085 ICUDataPath:[fixtures stringByAppendingString:@"/icudtl.dat"]];
2086 return [[
FlutterEngine alloc] initWithName:@"test" project:project allowHeadlessExecution:true];
2090 TEST(FlutterTextInputPluginTest, TestEmptyCompositionRange) {
2094 TEST(FlutterTextInputPluginTest, TestSetMarkedTextWithSelectionChange) {
2098 TEST(FlutterTextInputPluginTest, TestSetMarkedTextWithReplacementRange) {
2102 TEST(FlutterTextInputPluginTest, TestComposingRegionRemovedByFramework) {
2106 TEST(FlutterTextInputPluginTest, TestClearClientDuringComposing) {
2110 TEST(FlutterTextInputPluginTest, TestAutocompleteDisabledWhenAutofillNotSet) {
2114 TEST(FlutterTextInputPluginTest, TestAutocompleteEnabledWhenAutofillSet) {
2118 TEST(FlutterTextInputPluginTest, TestAutocompleteEnabledWhenAutofillSetNoHint) {
2122 TEST(FlutterTextInputPluginTest, TestAutocompleteDisabledWhenObscureTextSet) {
2126 TEST(FlutterTextInputPluginTest, TestAutocompleteDisabledWhenPasswordAutofillSet) {
2130 TEST(FlutterTextInputPluginTest, TestAutocompleteDisabledWhenAutofillGroupIncludesPassword) {
2132 testAutocompleteDisabledWhenAutofillGroupIncludesPassword]);
2135 TEST(FlutterTextInputPluginTest, TestFirstRectForCharacterRange) {
2139 TEST(FlutterTextInputPluginTest, TestFirstRectForCharacterRangeAtInfinity) {
2143 TEST(FlutterTextInputPluginTest, TestFirstRectForCharacterRangeWithEsotericAffineTransform) {
2145 testFirstRectForCharacterRangeWithEsotericAffineTransform]);
2148 TEST(FlutterTextInputPluginTest, TestSetEditingStateWithTextEditingDelta) {
2152 TEST(FlutterTextInputPluginTest, TestOperationsThatTriggerDelta) {
2156 TEST(FlutterTextInputPluginTest, TestComposingWithDelta) {
2160 TEST(FlutterTextInputPluginTest, TestComposingWithDeltasWhenSelectionIsActive) {
2164 TEST(FlutterTextInputPluginTest, TestLocalTextAndSelectionUpdateAfterDelta) {
2168 TEST(FlutterTextInputPluginTest, TestPerformKeyEquivalent) {
2172 TEST(FlutterTextInputPluginTest, HandleArrowKeyWhenImePopoverIsActive) {
2176 TEST(FlutterTextInputPluginTest, UnhandledKeyEquivalent) {
2180 TEST(FlutterTextInputPluginTest, TestSelectorsAreForwardedToFramework) {
2184 TEST(FlutterTextInputPluginTest, TestSelectorsNotForwardedToFrameworkIfNoClient) {
2188 TEST(FlutterTextInputPluginTest, TestInsertNewLine) {
2192 TEST(FlutterTextInputPluginTest, TestSendActionDoNotInsertNewLine) {
2196 TEST(FlutterTextInputPluginTest, TestAttributedSubstringOutOfRange) {
2200 [engineMock binaryMessenger])
2201 .andReturn(binaryMessengerMock);
2209 viewController:viewController];
2213 NSDictionary* setClientConfig = @{
2215 @"inputAction" :
@"action",
2216 @"enableDeltaModel" :
@"true",
2217 @"inputType" : @{
@"name" :
@"inputName"},
2227 @"selectionBase" : @(0),
2228 @"selectionExtent" : @(0),
2229 @"composingBase" : @(-1),
2230 @"composingExtent" : @(-1),
2238 NSAttributedString* text = [plugin attributedSubstringForProposedRange:NSMakeRange(1, 10)
2240 EXPECT_TRUE([text.string isEqualToString:
@"ext"]);
2241 EXPECT_EQ(out.location, 1u);
2242 EXPECT_EQ(out.length, 3u);
2244 text = [plugin attributedSubstringForProposedRange:NSMakeRange(4, 10) actualRange:&out];
2245 EXPECT_EQ(text, nil);
2248 TEST(FlutterTextInputPluginTest, CanWorkWithFlutterTextField) {
2253 [viewController loadView];
2255 NSWindow* window = [[NSWindow alloc] initWithContentRect:NSMakeRect(0, 0, 800, 600)
2256 styleMask:NSBorderlessWindowMask
2257 backing:NSBackingStoreBuffered
2259 window.contentView = viewController.view;
2261 engine.semanticsEnabled = YES;
2263 auto bridge = viewController.accessibilityBridge.lock();
2266 ui::AXNode ax_node(&tree,
nullptr, 0, 0);
2267 ui::AXNodeData node_data;
2268 node_data.SetValue(
"initial text");
2269 ax_node.SetData(node_data);
2270 delegate.
Init(viewController.accessibilityBridge, &ax_node);
2276 fieldEditor:engine.textInputPlugin];
2277 [viewController.view addSubview:mockTextField];
2280 NSDictionary* setClientConfig = @{
2281 @"viewId" : @(flutter::kFlutterImplicitViewId),
2282 @"inputAction" :
@"action",
2283 @"inputType" : @{
@"name" :
@"inputName"},
2290 [engine.textInputPlugin handleMethodCall:methodCall result:result];
2292 NSDictionary* arguments = @{
2293 @"text" :
@"new text",
2294 @"selectionBase" : @(1),
2295 @"selectionExtent" : @(2),
2296 @"composingBase" : @(-1),
2297 @"composingExtent" : @(-1),
2301 [engine.textInputPlugin handleMethodCall:methodCall result:result];
2307 [mockTextField setPlatformNode:nil];
2311 [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
2314 TEST(FlutterTextInputPluginTest, CanNotBecomeResponderIfNoViewController) {
2319 [viewController loadView];
2321 NSWindow* window = [[NSWindow alloc] initWithContentRect:NSMakeRect(0, 0, 800, 600)
2322 styleMask:NSBorderlessWindowMask
2323 backing:NSBackingStoreBuffered
2325 window.contentView = viewController.view;
2327 engine.semanticsEnabled = YES;
2329 auto bridge = viewController.accessibilityBridge.lock();
2332 ui::AXNode ax_node(&tree,
nullptr, 0, 0);
2333 ui::AXNodeData node_data;
2334 node_data.SetValue(
"initial text");
2335 ax_node.SetData(node_data);
2336 delegate.
Init(viewController.accessibilityBridge, &ax_node);
2340 EXPECT_EQ([textField becomeFirstResponder], YES);
2342 [engine setViewController:nil];
2345 EXPECT_EQ([textField becomeFirstResponder], NO);
2348 TEST(FlutterTextInputPluginTest, IsAddedAndRemovedFromViewHierarchy) {
2353 [viewController loadView];
2355 NSWindow* window = [[NSWindow alloc] initWithContentRect:NSMakeRect(0, 0, 800, 600)
2356 styleMask:NSBorderlessWindowMask
2357 backing:NSBackingStoreBuffered
2359 window.contentView = viewController.view;
2361 ASSERT_EQ(engine.textInputPlugin.superview, nil);
2362 ASSERT_FALSE(window.firstResponder == engine.textInputPlugin);
2364 NSDictionary* setClientConfig = @{
2365 @"viewId" : @(flutter::kFlutterImplicitViewId),
2367 [engine.textInputPlugin
2373 [engine.textInputPlugin
2378 ASSERT_EQ(engine.textInputPlugin.superview, viewController.view);
2379 ASSERT_TRUE(window.firstResponder == engine.textInputPlugin);
2381 [engine.textInputPlugin
2386 ASSERT_EQ(engine.textInputPlugin.superview, nil);
2387 ASSERT_FALSE(window.firstResponder == engine.textInputPlugin);
2390 TEST(FlutterTextInputPluginTest, FirstResponderIsCorrect) {
2395 [viewController loadView];
2397 NSWindow* window = [[NSWindow alloc] initWithContentRect:NSMakeRect(0, 0, 800, 600)
2398 styleMask:NSBorderlessWindowMask
2399 backing:NSBackingStoreBuffered
2401 window.contentView = viewController.view;
2403 ASSERT_TRUE(viewController.flutterView.acceptsFirstResponder);
2405 [window makeFirstResponder:viewController.flutterView];
2407 NSDictionary* setClientConfig = @{
2408 @"viewId" : @(flutter::kFlutterImplicitViewId),
2410 [engine.textInputPlugin
2416 [engine.textInputPlugin
2421 ASSERT_TRUE(window.firstResponder == engine.textInputPlugin);
2423 ASSERT_FALSE(viewController.flutterView.acceptsFirstResponder);
2425 [engine.textInputPlugin
2430 ASSERT_TRUE(viewController.flutterView.acceptsFirstResponder);
2431 ASSERT_TRUE(window.firstResponder == viewController.flutterView);
2434 TEST(FlutterTextInputPluginTest, HasZeroSizeAndClipsToBounds) {
2438 [engineMock binaryMessenger])
2439 .andReturn(binaryMessengerMock);
2447 viewController:viewController];
2451 ASSERT_TRUE(NSIsEmptyRect(plugin.frame));
2452 ASSERT_TRUE(plugin.clipsToBounds);
2455 TEST(FlutterTextInputPluginTest, WorksWithoutViewId) {
2459 [engineMock binaryMessenger])
2460 .andReturn(binaryMessengerMock);
2468 implicitViewController:viewController];
2472 NSDictionary* setClientConfig = @{
2483 TEST(FlutterTextInputPluginTest, InsertTextWithCollapsedSelectionInsideComposing) {
void(^ FlutterResult)(id _Nullable result)
FlutterBinaryMessengerRelay * _binaryMessenger
static const FlutterViewIdentifier kViewId
int64_t FlutterViewIdentifier
The ax platform node for a text field.
gfx::NativeViewAccessible GetNativeViewAccessible() override
instancetype methodCallWithMethodName:arguments:(NSString *method,[arguments] id _Nullable arguments)
NSRange lastUpdatedSelection
NSString * lastUpdatedString
NSString * customRunLoopMode
NSTextInputContext * textInputContext
FlutterViewController * currentViewController
NSRect firstRectForCharacterRange:actualRange:(NSRange range,[actualRange] NSRangePointer actualRange)
void handleMethodCall:result:(FlutterMethodCall *call,[result] FlutterResult result)
NSDictionary * editingState()
FlutterViewController * _implicitViewController
id< FlutterBinaryMessenger > _binaryMessenger
FlutterViewController * _viewController
TEST(FlutterTextInputPluginTest, InsertTextWithCollapsedSelectionInsideComposing)
id CreateMockFlutterEngine(NSString *pasteboardString)
id< FlutterBinaryMessenger > binaryMessenger