105 UIPasteboard.generalPasteboard.items = @[];
111 [textInputPlugin.autofillContext removeAllObjects];
112 [textInputPlugin cleanUpViewHierarchy:YES clearText:YES delayRemoval:NO];
113 [[[[textInputPlugin textInputView] superview] subviews]
114 makeObjectsPerformSelector:@selector(removeFromSuperview)];
119- (void)setClientId:(
int)clientId configuration:(NSDictionary*)config {
122 arguments:@[ [NSNumber numberWithInt:clientId], config ]];
123 [textInputPlugin handleMethodCall:setClientCall
124 result:^(id _Nullable result){
128- (void)setTextInputShow {
131 [textInputPlugin handleMethodCall:setClientCall
132 result:^(id _Nullable result){
136- (void)setTextInputHide {
139 [textInputPlugin handleMethodCall:setClientCall
140 result:^(id _Nullable result){
144- (void)flushScheduledAsyncBlocks {
145 __block
bool done =
false;
147 [[XCTestExpectation alloc] initWithDescription:@"Testing on main queue"];
148 dispatch_async(dispatch_get_main_queue(), ^{
151 dispatch_async(dispatch_get_main_queue(), ^{
153 [expectation fulfill];
155 [
self waitForExpectations:@[ expectation ] timeout:10];
158- (NSMutableDictionary*)mutableTemplateCopy {
161 @"inputType" : @{
@"name" :
@"TextInuptType.text"},
162 @"keyboardAppearance" :
@"Brightness.light",
163 @"obscureText" : @NO,
164 @"inputAction" :
@"TextInputAction.unspecified",
165 @"smartDashesType" :
@"0",
166 @"smartQuotesType" :
@"0",
167 @"autocorrect" : @YES,
168 @"enableInteractiveSelection" : @YES,
172 return [_template mutableCopy];
176 return (NSArray<FlutterTextInputView*>*)[textInputPlugin.textInputViews
177 filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"self isKindOfClass: %@",
181- (
FlutterTextRange*)getLineRangeFromTokenizer:(
id<UITextInputTokenizer>)tokenizer
182 atIndex:(NSInteger)index {
185 withGranularity:UITextGranularityLine
186 inDirection:UITextLayoutDirectionRight];
191- (void)updateConfig:(NSDictionary*)config {
194 [textInputPlugin handleMethodCall:updateConfigCall
195 result:^(id _Nullable result){
201- (void)testWillNotCrashWhenViewControllerIsNil {
208 XCTestExpectation*
expectation = [[XCTestExpectation alloc] initWithDescription:@"result called"];
211 result:^(id _Nullable result) {
212 XCTAssertNil(result);
213 [expectation fulfill];
216 [
self waitForExpectations:@[ expectation ] timeout:1.0];
219- (void)testInvokeStartLiveTextInput {
224 result:^(id _Nullable result){
226 OCMVerify([mockPlugin startLiveTextInput]);
229- (void)testNoDanglingEnginePointer {
239 weakFlutterEngine = flutterEngine;
240 NSAssert(weakFlutterEngine,
@"flutter engine must not be nil");
242 initWithDelegate:(id<FlutterTextInputDelegate>)flutterEngine];
243 weakFlutterTextInputPlugin = flutterTextInputPlugin;
247 NSDictionary* config =
self.mutableTemplateCopy;
250 arguments:@[ [NSNumber numberWithInt:123], config ]];
252 result:^(id _Nullable result){
254 currentView = flutterTextInputPlugin.
activeView;
257 NSAssert(!weakFlutterEngine,
@"flutter engine must be nil");
258 NSAssert(currentView,
@"current view must not be nil");
260 XCTAssertNil(weakFlutterTextInputPlugin);
266- (void)testSecureInput {
267 NSDictionary* config =
self.mutableTemplateCopy;
268 [config setValue:@"YES" forKey:@"obscureText"];
269 [
self setClientId:123 configuration:config];
272 NSArray<FlutterTextInputView*>* inputFields =
self.installedInputViews;
279 XCTAssertTrue(inputView.secureTextEntry);
282 XCTAssertEqual(inputView.keyboardType, UIKeyboardTypeDefault);
285 XCTAssertEqual(inputFields.count, 1ul);
296- (void)testKeyboardType {
297 NSDictionary* config =
self.mutableTemplateCopy;
298 [config setValue:@{@"name" : @"TextInputType.url"} forKey:@"inputType"];
299 [
self setClientId:123 configuration:config];
302 NSArray<FlutterTextInputView*>* inputFields =
self.installedInputViews;
307 XCTAssertEqual(inputView.keyboardType, UIKeyboardTypeURL);
310- (void)testVisiblePasswordUseAlphanumeric {
311 NSDictionary* config =
self.mutableTemplateCopy;
312 [config setValue:@{@"name" : @"TextInputType.visiblePassword"} forKey:@"inputType"];
313 [
self setClientId:123 configuration:config];
316 NSArray<FlutterTextInputView*>* inputFields =
self.installedInputViews;
321 XCTAssertEqual(inputView.keyboardType, UIKeyboardTypeASCIICapable);
324- (void)testSettingKeyboardTypeNoneDisablesSystemKeyboard {
325 NSDictionary* config =
self.mutableTemplateCopy;
326 [config setValue:@{@"name" : @"TextInputType.none"} forKey:@"inputType"];
327 [
self setClientId:123 configuration:config];
332 [config setValue:@{@"name" : @"TextInputType.url"} forKey:@"inputType"];
333 [
self setClientId:124 configuration:config];
338- (void)testAutocorrectionPromptRectAppearsBeforeIOS17AndDoesNotAppearAfterIOS17 {
342 if (@available(iOS 17.0, *)) {
344 OCMVerify(never(), [
engine flutterTextInputView:inputView
345 showAutocorrectionPromptRectForStart:0
349 OCMVerify([
engine flutterTextInputView:inputView
350 showAutocorrectionPromptRectForStart:0
356- (void)testIgnoresSelectionChangeIfSelectionIsDisabled {
358 __block
int updateCount = 0;
359 OCMStub([
engine flutterTextInputView:inputView updateEditingClient:0 withState:[OCMArg isNotNil]])
360 .andDo(^(NSInvocation* invocation) {
364 [inputView.text setString:@"Some initial text"];
365 XCTAssertEqual(updateCount, 0);
369 XCTAssertEqual(updateCount, 1);
372 NSDictionary* config =
self.mutableTemplateCopy;
373 [config setValue:@(NO) forKey:@"enableInteractiveSelection"];
374 [config setValue:@(NO) forKey:@"obscureText"];
375 [config setValue:@(NO) forKey:@"enableDeltaModel"];
381 XCTAssertEqual(updateCount, 1);
384- (void)testAutocorrectionPromptRectDoesNotAppearDuringScribble {
386 if (@available(iOS 17.0, *)) {
390 if (@available(iOS 14.0, *)) {
393 __block
int callCount = 0;
394 OCMStub([
engine flutterTextInputView:inputView
395 showAutocorrectionPromptRectForStart:0
398 .andDo(^(NSInvocation* invocation) {
404 XCTAssertEqual(callCount, 1);
406 UIScribbleInteraction* scribbleInteraction =
407 [[UIScribbleInteraction alloc] initWithDelegate:inputView];
413 XCTAssertEqual(callCount, 1);
419 XCTAssertEqual(callCount, 2);
421 inputView.scribbleFocusStatus = FlutterScribbleFocusStatusFocusing;
425 XCTAssertEqual(callCount, 2);
427 inputView.scribbleFocusStatus = FlutterScribbleFocusStatusFocused;
431 XCTAssertEqual(callCount, 2);
433 inputView.scribbleFocusStatus = FlutterScribbleFocusStatusUnfocused;
436 XCTAssertEqual(callCount, 3);
440- (void)testInputHiderOverlapWithTextWhenScribbleIsDisabledAfterIOS17AndDoesNotOverlapBeforeIOS17 {
446 arguments:@[ @(123),
self.mutableTemplateCopy ]];
448 result:^(id _Nullable result){
455 NSArray* yOffsetMatrix = @[ @1, @0, @0, @0, @0, @1, @0, @0, @0, @0, @1, @0, @0, @200, @0, @1 ];
459 arguments:@{@"transform" : yOffsetMatrix}];
461 result:^(id _Nullable result){
464 if (@available(iOS 17, *)) {
465 XCTAssert(CGRectEqualToRect(myInputPlugin.
inputHider.frame, CGRectMake(0, 200, 0, 0)),
466 @"The input hider should overlap with the text on and after iOS 17");
469 XCTAssert(CGRectEqualToRect(myInputPlugin.
inputHider.frame, CGRectZero),
470 @"The input hider should be on the origin of screen on and before iOS 16.");
474- (void)testTextRangeFromPositionMatchesUITextViewBehavior {
480 toPosition:toPosition];
481 NSRange range = flutterRange.
range;
483 XCTAssertEqual(range.location, 0ul);
484 XCTAssertEqual(range.length, 2ul);
487- (void)testTextInRange {
488 NSDictionary* config =
self.mutableTemplateCopy;
489 [config setValue:@{@"name" : @"TextInputType.url"} forKey:@"inputType"];
490 [
self setClientId:123 configuration:config];
491 NSArray<FlutterTextInputView*>* inputFields =
self.installedInputViews;
497 NSString* substring = [inputView
textInRange:range];
498 XCTAssertEqual(substring.length, 4ul);
502 XCTAssertEqual(substring.length, 0ul);
505- (void)testStandardEditActions {
506 NSDictionary* config =
self.mutableTemplateCopy;
507 [
self setClientId:123 configuration:config];
508 NSArray<FlutterTextInputView*>* inputFields =
self.installedInputViews;
515 XCTAssertTrue([inputView canPerformAction:@selector(paste:) withSender:nil]);
516 [inputView
paste:nil];
518 [inputView
copy:nil];
519 [inputView
paste:nil];
522 [inputView
paste:nil];
523 [inputView
paste:nil];
526 NSString* substring = [inputView
textInRange:range];
527 XCTAssertEqualObjects(substring,
@"bbbbaaaabbbbaaaa");
530- (void)testDeletingBackward {
531 NSDictionary* config =
self.mutableTemplateCopy;
532 [
self setClientId:123 configuration:config];
533 NSArray<FlutterTextInputView*>* inputFields =
self.installedInputViews;
536 [inputView
insertText:@"ឹ😀 text 🥰👨👩👧👦🇺🇳ดี "];
541 XCTAssertEqualObjects(inputView.text,
@"ឹ😀 text 🥰👨👩👧👦🇺🇳ด");
543 XCTAssertEqualObjects(inputView.text,
@"ឹ😀 text 🥰👨👩👧👦🇺🇳");
545 XCTAssertEqualObjects(inputView.text,
@"ឹ😀 text 🥰👨👩👧👦");
547 XCTAssertEqualObjects(inputView.text,
@"ឹ😀 text 🥰");
550 XCTAssertEqualObjects(inputView.text,
@"ឹ😀 text ");
558 XCTAssertEqualObjects(inputView.text,
@"ឹ😀");
560 XCTAssertEqualObjects(inputView.text,
@"ឹ");
562 XCTAssertEqualObjects(inputView.text,
@"");
567- (void)testSystemOnlyAddingPartialComposedCharacter {
568 NSDictionary* config =
self.mutableTemplateCopy;
569 [
self setClientId:123 configuration:config];
570 NSArray<FlutterTextInputView*>* inputFields =
self.installedInputViews;
577 [inputView
insertText:[@"👨👩👧👦" substringWithRange:NSMakeRange(0, 1)]];
580 XCTAssertEqualObjects(inputView.text,
@"👨👩👧👦아");
589 [inputView
insertText:[@"😀" substringWithRange:NSMakeRange(0, 1)]];
591 XCTAssertEqualObjects(inputView.text,
@"👨👩👧👦😀아");
599 [inputView
insertText:[@"😀" substringWithRange:NSMakeRange(0, 1)]];
602 XCTAssertEqualObjects(inputView.text,
@"👨👩👧👦😀아");
605- (void)testCachedComposedCharacterClearedAtKeyboardInteraction {
606 NSDictionary* config =
self.mutableTemplateCopy;
607 [
self setClientId:123 configuration:config];
608 NSArray<FlutterTextInputView*>* inputFields =
self.installedInputViews;
616 NSString* brokenEmoji = [@"👨👩👧👦" substringWithRange:NSMakeRange(0, 1)];
620 NSString* finalText = [NSString stringWithFormat:@"%@아", brokenEmoji];
621 XCTAssertEqualObjects(inputView.text, finalText);
624- (void)testPastingNonTextDisallowed {
625 NSDictionary* config =
self.mutableTemplateCopy;
626 [
self setClientId:123 configuration:config];
627 NSArray<FlutterTextInputView*>* inputFields =
self.installedInputViews;
630 UIPasteboard.generalPasteboard.color = UIColor.redColor;
631 XCTAssertNil(UIPasteboard.generalPasteboard.string);
632 XCTAssertFalse([inputView canPerformAction:@selector(paste:) withSender:nil]);
633 [inputView
paste:nil];
635 XCTAssertEqualObjects(inputView.text,
@"");
638- (void)testNoZombies {
645 [passwordView.textField description];
647 XCTAssert([[passwordView.
textField description] containsString:
@"TextField"]);
650- (void)testInputViewCrash {
655 initWithDelegate:(id<FlutterTextInputDelegate>)flutterEngine];
661- (void)testDoNotReuseInputViews {
662 NSDictionary* config =
self.mutableTemplateCopy;
663 [
self setClientId:123 configuration:config];
665 [
self setClientId:456 configuration:config];
667 XCTAssertNotNil(currentView);
672- (void)ensureOnlyActiveViewCanBecomeFirstResponder {
678- (void)testPropagatePressEventsToViewController {
680 OCMStub([mockViewController pressesBegan:[OCMArg isNotNil] withEvent:[OCMArg isNotNil]]);
681 OCMStub([mockViewController pressesEnded:[OCMArg isNotNil] withEvent:[OCMArg isNotNil]]);
685 NSDictionary* config =
self.mutableTemplateCopy;
686 [
self setClientId:123 configuration:config];
688 [
self setTextInputShow];
690 [currentView
pressesBegan:[NSSet setWithObjects:OCMClassMock([UIPress class]), nil]
691 withEvent:OCMClassMock([UIPressesEvent class])];
693 OCMVerify(
times(1), [mockViewController pressesBegan:[OCMArg isNotNil]
694 withEvent:[OCMArg isNotNil]]);
695 OCMVerify(
times(0), [mockViewController pressesEnded:[OCMArg isNotNil]
696 withEvent:[OCMArg isNotNil]]);
698 [currentView
pressesEnded:[NSSet setWithObjects:OCMClassMock([UIPress class]), nil]
699 withEvent:OCMClassMock([UIPressesEvent class])];
701 OCMVerify(
times(1), [mockViewController pressesBegan:[OCMArg isNotNil]
702 withEvent:[OCMArg isNotNil]]);
703 OCMVerify(
times(1), [mockViewController pressesEnded:[OCMArg isNotNil]
704 withEvent:[OCMArg isNotNil]]);
707- (void)testPropagatePressEventsToViewController2 {
709 OCMStub([mockViewController pressesBegan:[OCMArg isNotNil] withEvent:[OCMArg isNotNil]]);
710 OCMStub([mockViewController pressesEnded:[OCMArg isNotNil] withEvent:[OCMArg isNotNil]]);
714 NSDictionary* config =
self.mutableTemplateCopy;
715 [
self setClientId:123 configuration:config];
716 [
self setTextInputShow];
719 [currentView
pressesBegan:[NSSet setWithObjects:OCMClassMock([UIPress class]), nil]
720 withEvent:OCMClassMock([UIPressesEvent class])];
722 OCMVerify(
times(1), [mockViewController pressesBegan:[OCMArg isNotNil]
723 withEvent:[OCMArg isNotNil]]);
724 OCMVerify(
times(0), [mockViewController pressesEnded:[OCMArg isNotNil]
725 withEvent:[OCMArg isNotNil]]);
728 [
self setClientId:321 configuration:config];
729 [
self setTextInputShow];
733 [currentView
pressesEnded:[NSSet setWithObjects:OCMClassMock([UIPress class]), nil]
734 withEvent:OCMClassMock([UIPressesEvent class])];
736 OCMVerify(
times(1), [mockViewController pressesBegan:[OCMArg isNotNil]
737 withEvent:[OCMArg isNotNil]]);
738 OCMVerify(
times(1), [mockViewController pressesEnded:[OCMArg isNotNil]
739 withEvent:[OCMArg isNotNil]]);
742- (void)testUpdateSecureTextEntry {
743 NSDictionary* config =
self.mutableTemplateCopy;
744 [config setValue:@"YES" forKey:@"obscureText"];
745 [
self setClientId:123 configuration:config];
747 NSArray<FlutterTextInputView*>* inputFields =
self.installedInputViews;
750 __block
int callCount = 0;
751 OCMStub([inputView reloadInputViews]).andDo(^(NSInvocation* invocation) {
755 XCTAssertTrue(inputView.isSecureTextEntry);
757 config =
self.mutableTemplateCopy;
758 [config setValue:@"NO" forKey:@"obscureText"];
759 [
self updateConfig:config];
761 XCTAssertEqual(callCount, 1);
762 XCTAssertFalse(inputView.isSecureTextEntry);
765- (void)testInputActionContinueAction {
781 arguments:@[ @(123), @"TextInputAction.continueAction" ]];
783 OCMVerify([mockBinaryMessenger sendOnChannel:
@"flutter/textinput" message:encodedMethodCall]);
786- (void)testDisablingAutocorrectDisablesSpellChecking {
790 NSDictionary* config =
self.mutableTemplateCopy;
793 XCTAssertEqual(inputView.autocorrectionType, UITextAutocorrectionTypeDefault);
794 XCTAssertEqual(inputView.spellCheckingType, UITextSpellCheckingTypeDefault);
796 [config setValue:@(NO) forKey:@"autocorrect"];
799 XCTAssertEqual(inputView.autocorrectionType, UITextAutocorrectionTypeNo);
800 XCTAssertEqual(inputView.spellCheckingType, UITextSpellCheckingTypeNo);
803- (void)testReplaceTestLocalAdjustSelectionAndMarkedTextRange {
819 XCTAssertEqual(inputView.markedTextRange, nil);
822- (void)testFlutterTextInputViewOnlyRespondsToInsertionPointColorBelowIOS17 {
824 BOOL respondsToInsertionPointColor =
826 if (@available(iOS 17, *)) {
827 XCTAssertFalse(respondsToInsertionPointColor);
829 XCTAssertTrue(respondsToInsertionPointColor);
833#pragma mark - TextEditingDelta tests
834- (void)testTextEditingDeltasAreGeneratedOnTextInput {
836 inputView.enableDeltaModel = YES;
838 __block
int updateCount = 0;
843 flutterTextInputView:inputView
844 updateEditingClient:0
845 withDelta:[OCMArg checkWithBlock:^
BOOL(NSDictionary*
state) {
846 return ([[
state[
@"deltas"] objectAtIndex:0][
@"oldText"]
847 isEqualToString:
@""]) &&
848 ([[
state[
@"deltas"] objectAtIndex:0][
@"deltaText"]
849 isEqualToString:
@"text to insert"]) &&
850 ([[
state[
@"deltas"] objectAtIndex:0][
@"deltaStart"] intValue] == 0) &&
851 ([[
state[
@"deltas"] objectAtIndex:0][
@"deltaEnd"] intValue] == 0);
853 .andDo(^(NSInvocation* invocation) {
856 XCTAssertEqual(updateCount, 0);
858 [
self flushScheduledAsyncBlocks];
861 XCTAssertEqual(updateCount, 1);
864 OCMExpect([
engine flutterTextInputView:inputView
865 updateEditingClient:0
866 withDelta:[OCMArg checkWithBlock:^
BOOL(NSDictionary*
state) {
867 return ([[
state[
@"deltas"] objectAtIndex:0][
@"oldText"]
868 isEqualToString:
@"text to insert"]) &&
869 ([[
state[
@"deltas"] objectAtIndex:0][
@"deltaText"]
870 isEqualToString:
@""]) &&
871 ([[
state[
@"deltas"] objectAtIndex:0][
@"deltaStart"]
873 ([[
state[
@"deltas"] objectAtIndex:0][
@"deltaEnd"]
876 .andDo(^(NSInvocation* invocation) {
879 [
self flushScheduledAsyncBlocks];
880 XCTAssertEqual(updateCount, 2);
883 OCMExpect([
engine flutterTextInputView:inputView
884 updateEditingClient:0
885 withDelta:[OCMArg checkWithBlock:^
BOOL(NSDictionary*
state) {
886 return ([[
state[
@"deltas"] objectAtIndex:0][
@"oldText"]
887 isEqualToString:
@"text to inser"]) &&
888 ([[
state[
@"deltas"] objectAtIndex:0][
@"deltaText"]
889 isEqualToString:
@""]) &&
890 ([[
state[
@"deltas"] objectAtIndex:0][
@"deltaStart"]
892 ([[
state[
@"deltas"] objectAtIndex:0][
@"deltaEnd"]
895 .andDo(^(NSInvocation* invocation) {
898 [
self flushScheduledAsyncBlocks];
899 XCTAssertEqual(updateCount, 3);
905 flutterTextInputView:inputView
906 updateEditingClient:0
907 withDelta:[OCMArg checkWithBlock:^
BOOL(NSDictionary*
state) {
908 return ([[
state[
@"deltas"] objectAtIndex:0][
@"oldText"]
909 isEqualToString:
@"text to inser"]) &&
910 ([[
state[
@"deltas"] objectAtIndex:0][
@"deltaText"]
911 isEqualToString:
@"replace text"]) &&
912 ([[
state[
@"deltas"] objectAtIndex:0][
@"deltaStart"] intValue] == 0) &&
913 ([[
state[
@"deltas"] objectAtIndex:0][
@"deltaEnd"] intValue] == 1);
915 .andDo(^(NSInvocation* invocation) {
918 [
self flushScheduledAsyncBlocks];
919 XCTAssertEqual(updateCount, 4);
922 OCMExpect([
engine flutterTextInputView:inputView
923 updateEditingClient:0
924 withDelta:[OCMArg checkWithBlock:^
BOOL(NSDictionary*
state) {
925 return ([[
state[
@"deltas"] objectAtIndex:0][
@"oldText"]
926 isEqualToString:
@"replace textext to inser"]) &&
927 ([[
state[
@"deltas"] objectAtIndex:0][
@"deltaText"]
928 isEqualToString:
@"marked text"]) &&
929 ([[
state[
@"deltas"] objectAtIndex:0][
@"deltaStart"]
931 ([[
state[
@"deltas"] objectAtIndex:0][
@"deltaEnd"]
934 .andDo(^(NSInvocation* invocation) {
937 [
self flushScheduledAsyncBlocks];
938 XCTAssertEqual(updateCount, 5);
942 flutterTextInputView:inputView
943 updateEditingClient:0
944 withDelta:[OCMArg checkWithBlock:^
BOOL(NSDictionary*
state) {
945 return ([[
state[
@"deltas"] objectAtIndex:0][
@"oldText"]
946 isEqualToString:
@"replace textmarked textext to inser"]) &&
947 ([[
state[
@"deltas"] objectAtIndex:0][
@"deltaText"]
948 isEqualToString:
@""]) &&
949 ([[
state[
@"deltas"] objectAtIndex:0][
@"deltaStart"] intValue] ==
951 ([[
state[
@"deltas"] objectAtIndex:0][
@"deltaEnd"] intValue] ==
954 .andDo(^(NSInvocation* invocation) {
957 [
self flushScheduledAsyncBlocks];
959 XCTAssertEqual(updateCount, 6);
963- (void)testTextEditingDeltasAreBatchedAndForwardedToFramework {
966 inputView.enableDeltaModel = YES;
969 OCMExpect([
engine flutterTextInputView:inputView
970 updateEditingClient:0
971 withDelta:[OCMArg checkWithBlock:^
BOOL(NSDictionary*
state) {
972 NSArray* deltas =
state[@"deltas"];
973 NSDictionary* firstDelta = deltas[0];
974 NSDictionary* secondDelta = deltas[1];
975 NSDictionary* thirdDelta = deltas[2];
976 return [firstDelta[@"oldText"] isEqualToString:@""] &&
977 [firstDelta[@"deltaText"] isEqualToString:@"-"] &&
978 [firstDelta[@"deltaStart"] intValue] == 0 &&
979 [firstDelta[@"deltaEnd"] intValue] == 0 &&
980 [secondDelta[@"oldText"] isEqualToString:@"-"] &&
981 [secondDelta[@"deltaText"] isEqualToString:@""] &&
982 [secondDelta[@"deltaStart"] intValue] == 0 &&
983 [secondDelta[@"deltaEnd"] intValue] == 1 &&
984 [thirdDelta[@"oldText"] isEqualToString:@""] &&
985 [thirdDelta[@"deltaText"] isEqualToString:@"—"] &&
986 [thirdDelta[@"deltaStart"] intValue] == 0 &&
987 [thirdDelta[@"deltaEnd"] intValue] == 0;
995 [
self flushScheduledAsyncBlocks];
999- (void)testTextEditingDeltasAreGeneratedOnSetMarkedTextReplacement {
1001 inputView.enableDeltaModel = YES;
1003 __block
int updateCount = 0;
1004 OCMStub([
engine flutterTextInputView:inputView updateEditingClient:0 withDelta:[OCMArg isNotNil]])
1005 .andDo(^(NSInvocation* invocation) {
1009 [inputView.text setString:@"Some initial text"];
1010 XCTAssertEqual(updateCount, 0);
1013 inputView.markedTextRange = range;
1015 [
self flushScheduledAsyncBlocks];
1016 XCTAssertEqual(updateCount, 1);
1020 flutterTextInputView:inputView
1021 updateEditingClient:0
1022 withDelta:[OCMArg checkWithBlock:^
BOOL(NSDictionary*
state) {
1023 return ([[
state[
@"deltas"] objectAtIndex:0][
@"oldText"]
1024 isEqualToString:
@"Some initial text"]) &&
1025 ([[
state[
@"deltas"] objectAtIndex:0][
@"deltaText"]
1026 isEqualToString:
@"new marked text."]) &&
1027 ([[
state[
@"deltas"] objectAtIndex:0][
@"deltaStart"] intValue] == 13) &&
1028 ([[
state[
@"deltas"] objectAtIndex:0][
@"deltaEnd"] intValue] == 17);
1030 [
self flushScheduledAsyncBlocks];
1031 XCTAssertEqual(updateCount, 2);
1034- (void)testTextEditingDeltasAreGeneratedOnSetMarkedTextInsertion {
1036 inputView.enableDeltaModel = YES;
1038 __block
int updateCount = 0;
1039 OCMStub([
engine flutterTextInputView:inputView updateEditingClient:0 withDelta:[OCMArg isNotNil]])
1040 .andDo(^(NSInvocation* invocation) {
1044 [inputView.text setString:@"Some initial text"];
1045 [
self flushScheduledAsyncBlocks];
1046 XCTAssertEqual(updateCount, 0);
1049 inputView.markedTextRange = range;
1051 [
self flushScheduledAsyncBlocks];
1052 XCTAssertEqual(updateCount, 1);
1056 flutterTextInputView:inputView
1057 updateEditingClient:0
1058 withDelta:[OCMArg checkWithBlock:^
BOOL(NSDictionary*
state) {
1059 return ([[
state[
@"deltas"] objectAtIndex:0][
@"oldText"]
1060 isEqualToString:
@"Some initial text"]) &&
1061 ([[
state[
@"deltas"] objectAtIndex:0][
@"deltaText"]
1062 isEqualToString:
@"text."]) &&
1063 ([[
state[
@"deltas"] objectAtIndex:0][
@"deltaStart"] intValue] == 13) &&
1064 ([[
state[
@"deltas"] objectAtIndex:0][
@"deltaEnd"] intValue] == 17);
1066 [
self flushScheduledAsyncBlocks];
1067 XCTAssertEqual(updateCount, 2);
1070- (void)testTextEditingDeltasAreGeneratedOnSetMarkedTextDeletion {
1072 inputView.enableDeltaModel = YES;
1074 __block
int updateCount = 0;
1075 OCMStub([
engine flutterTextInputView:inputView updateEditingClient:0 withDelta:[OCMArg isNotNil]])
1076 .andDo(^(NSInvocation* invocation) {
1080 [inputView.text setString:@"Some initial text"];
1081 [
self flushScheduledAsyncBlocks];
1082 XCTAssertEqual(updateCount, 0);
1085 inputView.markedTextRange = range;
1087 [
self flushScheduledAsyncBlocks];
1088 XCTAssertEqual(updateCount, 1);
1092 flutterTextInputView:inputView
1093 updateEditingClient:0
1094 withDelta:[OCMArg checkWithBlock:^
BOOL(NSDictionary*
state) {
1095 return ([[
state[
@"deltas"] objectAtIndex:0][
@"oldText"]
1096 isEqualToString:
@"Some initial text"]) &&
1097 ([[
state[
@"deltas"] objectAtIndex:0][
@"deltaText"]
1098 isEqualToString:
@"tex"]) &&
1099 ([[
state[
@"deltas"] objectAtIndex:0][
@"deltaStart"] intValue] == 13) &&
1100 ([[
state[
@"deltas"] objectAtIndex:0][
@"deltaEnd"] intValue] == 17);
1102 [
self flushScheduledAsyncBlocks];
1103 XCTAssertEqual(updateCount, 2);
1106#pragma mark - EditingState tests
1108- (void)testUITextInputCallsUpdateEditingStateOnce {
1111 __block
int updateCount = 0;
1112 OCMStub([
engine flutterTextInputView:inputView updateEditingClient:0 withState:[OCMArg isNotNil]])
1113 .andDo(^(NSInvocation* invocation) {
1119 XCTAssertEqual(updateCount, 1);
1122 XCTAssertEqual(updateCount, 2);
1125 XCTAssertEqual(updateCount, 3);
1129 XCTAssertEqual(updateCount, 4);
1132 XCTAssertEqual(updateCount, 5);
1135 XCTAssertEqual(updateCount, 6);
1138- (void)testUITextInputCallsUpdateEditingStateWithDeltaOnce {
1140 inputView.enableDeltaModel = YES;
1142 __block
int updateCount = 0;
1143 OCMStub([
engine flutterTextInputView:inputView updateEditingClient:0 withDelta:[OCMArg isNotNil]])
1144 .andDo(^(NSInvocation* invocation) {
1149 [
self flushScheduledAsyncBlocks];
1151 XCTAssertEqual(updateCount, 1);
1154 [
self flushScheduledAsyncBlocks];
1155 XCTAssertEqual(updateCount, 2);
1158 [
self flushScheduledAsyncBlocks];
1159 XCTAssertEqual(updateCount, 3);
1163 [
self flushScheduledAsyncBlocks];
1164 XCTAssertEqual(updateCount, 4);
1167 [
self flushScheduledAsyncBlocks];
1168 XCTAssertEqual(updateCount, 5);
1171 [
self flushScheduledAsyncBlocks];
1172 XCTAssertEqual(updateCount, 6);
1175- (void)testTextChangesDoNotTriggerUpdateEditingClient {
1178 __block
int updateCount = 0;
1179 OCMStub([
engine flutterTextInputView:inputView updateEditingClient:0 withState:[OCMArg isNotNil]])
1180 .andDo(^(NSInvocation* invocation) {
1184 [inputView.text setString:@"BEFORE"];
1185 XCTAssertEqual(updateCount, 0);
1187 inputView.markedTextRange = nil;
1189 XCTAssertEqual(updateCount, 1);
1192 XCTAssertEqual(updateCount, 1);
1194 XCTAssertEqual(updateCount, 1);
1196 XCTAssertEqual(updateCount, 1);
1200 setTextInputState:@{@"text" : @"SELECTION", @"selectionBase" : @0, @"selectionExtent" : @3}];
1201 XCTAssertEqual(updateCount, 1);
1203 setTextInputState:@{@"text" : @"SELECTION", @"selectionBase" : @1, @"selectionExtent" : @3}];
1204 XCTAssertEqual(updateCount, 1);
1208 setTextInputState:@{@"text" : @"COMPOSING", @"composingBase" : @1, @"composingExtent" : @2}];
1209 XCTAssertEqual(updateCount, 1);
1211 setTextInputState:@{@"text" : @"COMPOSING", @"composingBase" : @1, @"composingExtent" : @3}];
1212 XCTAssertEqual(updateCount, 1);
1215- (void)testTextChangesDoNotTriggerUpdateEditingClientWithDelta {
1217 inputView.enableDeltaModel = YES;
1219 __block
int updateCount = 0;
1220 OCMStub([
engine flutterTextInputView:inputView updateEditingClient:0 withDelta:[OCMArg isNotNil]])
1221 .andDo(^(NSInvocation* invocation) {
1225 [inputView.text setString:@"BEFORE"];
1226 [
self flushScheduledAsyncBlocks];
1227 XCTAssertEqual(updateCount, 0);
1229 inputView.markedTextRange = nil;
1231 [
self flushScheduledAsyncBlocks];
1232 XCTAssertEqual(updateCount, 1);
1235 XCTAssertEqual(updateCount, 1);
1237 [
self flushScheduledAsyncBlocks];
1238 XCTAssertEqual(updateCount, 1);
1241 [
self flushScheduledAsyncBlocks];
1242 XCTAssertEqual(updateCount, 1);
1246 setTextInputState:@{@"text" : @"SELECTION", @"selectionBase" : @0, @"selectionExtent" : @3}];
1247 [
self flushScheduledAsyncBlocks];
1248 XCTAssertEqual(updateCount, 1);
1251 setTextInputState:@{@"text" : @"SELECTION", @"selectionBase" : @1, @"selectionExtent" : @3}];
1252 [
self flushScheduledAsyncBlocks];
1253 XCTAssertEqual(updateCount, 1);
1257 setTextInputState:@{@"text" : @"COMPOSING", @"composingBase" : @1, @"composingExtent" : @2}];
1258 [
self flushScheduledAsyncBlocks];
1259 XCTAssertEqual(updateCount, 1);
1262 setTextInputState:@{@"text" : @"COMPOSING", @"composingBase" : @1, @"composingExtent" : @3}];
1263 [
self flushScheduledAsyncBlocks];
1264 XCTAssertEqual(updateCount, 1);
1267- (void)testUITextInputAvoidUnnecessaryUndateEditingClientCalls {
1270 __block
int updateCount = 0;
1271 OCMStub([
engine flutterTextInputView:inputView updateEditingClient:0 withState:[OCMArg isNotNil]])
1272 .andDo(^(NSInvocation* invocation) {
1278 XCTAssertEqual(updateCount, 0);
1282 XCTAssertEqual(updateCount, 1);
1286 XCTAssertEqual(updateCount, 2);
1289- (void)testCanCopyPasteWithScribbleEnabled {
1290 if (@available(iOS 14.0, *)) {
1291 NSDictionary* config =
self.mutableTemplateCopy;
1292 [
self setClientId:123 configuration:config];
1293 NSArray<FlutterTextInputView*>* inputFields =
self.installedInputViews;
1302 XCTAssertTrue([mockInputView canPerformAction:@selector(
copy:) withSender:NULL]);
1303 XCTAssertTrue([mockInputView canPerformAction:@selector(
copy:) withSender:
@"sender"]);
1304 XCTAssertFalse([mockInputView canPerformAction:@selector(paste:) withSender:NULL]);
1305 XCTAssertFalse([mockInputView canPerformAction:@selector(paste:) withSender:
@"sender"]);
1307 [mockInputView
copy:NULL];
1308 XCTAssertTrue([mockInputView canPerformAction:@selector(
copy:) withSender:NULL]);
1309 XCTAssertTrue([mockInputView canPerformAction:@selector(
copy:) withSender:
@"sender"]);
1310 XCTAssertTrue([mockInputView canPerformAction:@selector(paste:) withSender:NULL]);
1311 XCTAssertTrue([mockInputView canPerformAction:@selector(paste:) withSender:
@"sender"]);
1315- (void)testSetMarkedTextDuringScribbleDoesNotTriggerUpdateEditingClient {
1316 if (@available(iOS 14.0, *)) {
1319 __block
int updateCount = 0;
1320 OCMStub([
engine flutterTextInputView:inputView
1321 updateEditingClient:0
1322 withState:[OCMArg isNotNil]])
1323 .andDo(^(NSInvocation* invocation) {
1329 XCTAssertEqual(updateCount, 1);
1331 UIScribbleInteraction* scribbleInteraction =
1332 [[UIScribbleInteraction alloc] initWithDelegate:inputView];
1337 XCTAssertEqual(updateCount, 1);
1343 XCTAssertEqual(updateCount, 2);
1345 inputView.scribbleFocusStatus = FlutterScribbleFocusStatusFocusing;
1349 XCTAssertEqual(updateCount, 2);
1351 inputView.scribbleFocusStatus = FlutterScribbleFocusStatusFocused;
1355 XCTAssertEqual(updateCount, 2);
1357 inputView.scribbleFocusStatus = FlutterScribbleFocusStatusUnfocused;
1360 XCTAssertEqual(updateCount, 3);
1364- (void)testUpdateEditingClientNegativeSelection {
1367 [inputView.text setString:@"SELECTION"];
1368 inputView.markedTextRange = nil;
1372 @"text" : @"SELECTION",
1373 @"selectionBase" : @-1,
1374 @"selectionExtent" : @-1
1377 OCMVerify([
engine flutterTextInputView:inputView
1378 updateEditingClient:0
1379 withState:[OCMArg checkWithBlock:^
BOOL(NSDictionary*
state) {
1380 return ([
state[
@"selectionBase"] intValue]) == 0 &&
1381 ([state[@"selectionExtent"] intValue] == 0);
1386 setTextInputState:@{@"text" : @"SELECTION", @"selectionBase" : @-1, @"selectionExtent" : @1}];
1388 OCMVerify([
engine flutterTextInputView:inputView
1389 updateEditingClient:0
1390 withState:[OCMArg checkWithBlock:^
BOOL(NSDictionary*
state) {
1391 return ([
state[
@"selectionBase"] intValue]) == 0 &&
1392 ([state[@"selectionExtent"] intValue] == 0);
1396 setTextInputState:@{@"text" : @"SELECTION", @"selectionBase" : @1, @"selectionExtent" : @-1}];
1398 OCMVerify([
engine flutterTextInputView:inputView
1399 updateEditingClient:0
1400 withState:[OCMArg checkWithBlock:^
BOOL(NSDictionary*
state) {
1401 return ([
state[
@"selectionBase"] intValue]) == 0 &&
1402 ([state[@"selectionExtent"] intValue] == 0);
1406- (void)testUpdateEditingClientSelectionClamping {
1410 [inputView.text setString:@"SELECTION"];
1411 inputView.markedTextRange = nil;
1415 setTextInputState:@{@"text" : @"SELECTION", @"selectionBase" : @0, @"selectionExtent" : @0}];
1417 OCMVerify([
engine flutterTextInputView:inputView
1418 updateEditingClient:0
1419 withState:[OCMArg checkWithBlock:^
BOOL(NSDictionary*
state) {
1420 return ([
state[
@"selectionBase"] intValue]) == 0 &&
1421 ([state[@"selectionExtent"] intValue] == 0);
1426 @"text" : @"SELECTION",
1427 @"selectionBase" : @0,
1428 @"selectionExtent" : @9999
1432 OCMVerify([
engine flutterTextInputView:inputView
1433 updateEditingClient:0
1434 withState:[OCMArg checkWithBlock:^
BOOL(NSDictionary*
state) {
1435 return ([
state[
@"selectionBase"] intValue]) == 0 &&
1436 ([state[@"selectionExtent"] intValue] == 9);
1441 setTextInputState:@{@"text" : @"SELECTION", @"selectionBase" : @1, @"selectionExtent" : @0}];
1443 OCMVerify([
engine flutterTextInputView:inputView
1444 updateEditingClient:0
1445 withState:[OCMArg checkWithBlock:^
BOOL(NSDictionary*
state) {
1446 return ([
state[
@"selectionBase"] intValue]) == 0 &&
1447 ([state[@"selectionExtent"] intValue] == 1);
1452 @"text" : @"SELECTION",
1453 @"selectionBase" : @9999,
1454 @"selectionExtent" : @9999
1457 OCMVerify([
engine flutterTextInputView:inputView
1458 updateEditingClient:0
1459 withState:[OCMArg checkWithBlock:^
BOOL(NSDictionary*
state) {
1460 return ([
state[
@"selectionBase"] intValue]) == 9 &&
1461 ([state[@"selectionExtent"] intValue] == 9);
1465- (void)testInputViewsHasNonNilInputDelegate {
1466 if (@available(iOS 13.0, *)) {
1468 [UIApplication.sharedApplication.keyWindow addSubview:inputView];
1471 [inputView reloadInputViews];
1472 [inputView becomeFirstResponder];
1473 NSAssert(inputView.isFirstResponder,
@"inputView is not first responder");
1474 inputView.inputDelegate = nil;
1478 @"text" : @"COMPOSING",
1479 @"composingBase" : @1,
1480 @"composingExtent" : @3
1482 OCMVerify([mockInputView setInputDelegate:[OCMArg isNotNil]]);
1483 [inputView removeFromSuperview];
1487- (void)testInputViewsDoNotHaveUITextInteractions {
1488 if (@available(iOS 13.0, *)) {
1490 BOOL hasTextInteraction = NO;
1491 for (
id interaction in inputView.interactions) {
1492 hasTextInteraction = [interaction isKindOfClass:[UITextInteraction class]];
1493 if (hasTextInteraction) {
1497 XCTAssertFalse(hasTextInteraction);
1501#pragma mark - UITextInput methods - Tests
1503- (void)testUpdateFirstRectForRange {
1504 [
self setClientId:123 configuration:self.mutableTemplateCopy];
1510 setTextInputState:@{@"text" : @"COMPOSING", @"composingBase" : @1, @"composingExtent" : @3}];
1515 NSArray* yOffsetMatrix = @[ @1, @0, @0, @0, @0, @1, @0, @0, @0, @0, @1, @0, @0, @200, @0, @1 ];
1516 NSArray* zeroMatrix = @[ @0, @0, @0, @0, @0, @0, @0, @0, @0, @0, @0, @0, @0, @0, @0, @0 ];
1520 NSArray* affineMatrix = @[
1521 @(0.0), @(3.0), @(0.0), @(0.0), @(-3.0), @(0.0), @(0.0), @(0.0), @(0.0), @(0.0), @(3.0), @(0.0),
1522 @(-6.0), @(3.0), @(9.0), @(1.0)
1526 XCTAssertTrue(CGRectEqualToRect(
kInvalidFirstRect, [inputView firstRectForRange:range]));
1530 XCTAssertTrue(CGRectEqualToRect(
kInvalidFirstRect, [inputView firstRectForRange:range]));
1533 CGRect testRect = CGRectMake(0, 0, 100, 100);
1536 CGRect finalRect = CGRectOffset(testRect, 0, 200);
1537 XCTAssertTrue(CGRectEqualToRect(finalRect, [inputView firstRectForRange:range]));
1539 XCTAssertTrue(CGRectEqualToRect(finalRect, [inputView firstRectForRange:range]));
1544 XCTAssertTrue(CGRectEqualToRect(
kInvalidFirstRect, [inputView firstRectForRange:range]));
1545 XCTAssertTrue(CGRectEqualToRect(
kInvalidFirstRect, [inputView firstRectForRange:range]));
1550 XCTAssertTrue(CGRectEqualToRect(finalRect, [inputView firstRectForRange:range]));
1555 XCTAssertTrue(CGRectEqualToRect(
kInvalidFirstRect, [inputView firstRectForRange:range]));
1556 XCTAssertTrue(CGRectEqualToRect(
kInvalidFirstRect, [inputView firstRectForRange:range]));
1562 CGRectEqualToRect(CGRectMake(-306, 3, 300, 300), [inputView firstRectForRange:range]));
1564 NSAssert(inputView.superview,
@"inputView is not in the view hierarchy!");
1565 const CGPoint
offset = CGPointMake(113, 119);
1566 CGRect currentFrame = inputView.frame;
1567 currentFrame.origin =
offset;
1568 inputView.frame = currentFrame;
1571 XCTAssertTrue(CGRectEqualToRect(CGRectMake(-306 - 113, 3 - 119, 300, 300),
1572 [inputView firstRectForRange:range]));
1575- (void)testFirstRectForRangeReturnsNoneZeroRectWhenScribbleIsEnabled {
1582 [inputView setSelectionRects:@[
1591 if (@available(iOS 17, *)) {
1592 XCTAssertTrue(CGRectEqualToRect(CGRectMake(100, 0, 300, 100),
1593 [inputView firstRectForRange:multiRectRange]));
1595 XCTAssertTrue(CGRectEqualToRect(CGRectMake(100, 0, 100, 100),
1596 [inputView firstRectForRange:multiRectRange]));
1600- (void)testFirstRectForRangeReturnsCorrectRectOnASingleLineLeftToRight {
1604 [inputView setSelectionRects:@[
1611 if (@available(iOS 17, *)) {
1612 XCTAssertTrue(CGRectEqualToRect(CGRectMake(100, 0, 100, 100),
1613 [inputView firstRectForRange:singleRectRange]));
1615 XCTAssertTrue(CGRectEqualToRect(CGRectZero, [inputView firstRectForRange:singleRectRange]));
1620 if (@available(iOS 17, *)) {
1621 XCTAssertTrue(CGRectEqualToRect(CGRectMake(100, 0, 300, 100),
1622 [inputView firstRectForRange:multiRectRange]));
1624 XCTAssertTrue(CGRectEqualToRect(CGRectZero, [inputView firstRectForRange:multiRectRange]));
1629 XCTAssertTrue(CGRectEqualToRect(CGRectZero, [inputView firstRectForRange:rangeOutsideBounds]));
1632- (void)testFirstRectForRangeReturnsCorrectRectOnASingleLineRightToLeft {
1636 [inputView setSelectionRects:@[
1643 if (@available(iOS 17, *)) {
1644 XCTAssertTrue(CGRectEqualToRect(CGRectMake(200, 0, 100, 100),
1645 [inputView firstRectForRange:singleRectRange]));
1647 XCTAssertTrue(CGRectEqualToRect(CGRectZero, [inputView firstRectForRange:singleRectRange]));
1651 if (@available(iOS 17, *)) {
1652 XCTAssertTrue(CGRectEqualToRect(CGRectMake(0, 0, 300, 100),
1653 [inputView firstRectForRange:multiRectRange]));
1655 XCTAssertTrue(CGRectEqualToRect(CGRectZero, [inputView firstRectForRange:multiRectRange]));
1660 XCTAssertTrue(CGRectEqualToRect(CGRectZero, [inputView firstRectForRange:rangeOutsideBounds]));
1663- (void)testFirstRectForRangeReturnsCorrectRectOnMultipleLinesLeftToRight {
1667 [inputView setSelectionRects:@[
1678 if (@available(iOS 17, *)) {
1679 XCTAssertTrue(CGRectEqualToRect(CGRectMake(100, 0, 100, 100),
1680 [inputView firstRectForRange:singleRectRange]));
1682 XCTAssertTrue(CGRectEqualToRect(CGRectZero, [inputView firstRectForRange:singleRectRange]));
1687 if (@available(iOS 17, *)) {
1688 XCTAssertTrue(CGRectEqualToRect(CGRectMake(100, 0, 300, 100),
1689 [inputView firstRectForRange:multiRectRange]));
1691 XCTAssertTrue(CGRectEqualToRect(CGRectZero, [inputView firstRectForRange:multiRectRange]));
1695- (void)testFirstRectForRangeReturnsCorrectRectOnMultipleLinesRightToLeft {
1699 [inputView setSelectionRects:@[
1710 if (@available(iOS 17, *)) {
1711 XCTAssertTrue(CGRectEqualToRect(CGRectMake(200, 0, 100, 100),
1712 [inputView firstRectForRange:singleRectRange]));
1714 XCTAssertTrue(CGRectEqualToRect(CGRectZero, [inputView firstRectForRange:singleRectRange]));
1718 if (@available(iOS 17, *)) {
1719 XCTAssertTrue(CGRectEqualToRect(CGRectMake(0, 0, 300, 100),
1720 [inputView firstRectForRange:multiRectRange]));
1722 XCTAssertTrue(CGRectEqualToRect(CGRectZero, [inputView firstRectForRange:multiRectRange]));
1726- (void)testFirstRectForRangeReturnsCorrectRectOnSingleLineWithVaryingMinYAndMaxYLeftToRight {
1730 [inputView setSelectionRects:@[
1741 if (@available(iOS 17, *)) {
1742 XCTAssertTrue(CGRectEqualToRect(CGRectMake(100, -10, 300, 120),
1743 [inputView firstRectForRange:multiRectRange]));
1745 XCTAssertTrue(CGRectEqualToRect(CGRectZero, [inputView firstRectForRange:multiRectRange]));
1749- (void)testFirstRectForRangeReturnsCorrectRectOnSingleLineWithVaryingMinYAndMaxYRightToLeft {
1753 [inputView setSelectionRects:@[
1764 if (@available(iOS 17, *)) {
1765 XCTAssertTrue(CGRectEqualToRect(CGRectMake(0, -10, 300, 120),
1766 [inputView firstRectForRange:multiRectRange]));
1768 XCTAssertTrue(CGRectEqualToRect(CGRectZero, [inputView firstRectForRange:multiRectRange]));
1772- (void)testFirstRectForRangeReturnsCorrectRectWithOverlappingRectsExceedingThresholdLeftToRight {
1776 [inputView setSelectionRects:@[
1787 if (@available(iOS 17, *)) {
1788 XCTAssertTrue(CGRectEqualToRect(CGRectMake(100, 0, 300, 100),
1789 [inputView firstRectForRange:multiRectRange]));
1791 XCTAssertTrue(CGRectEqualToRect(CGRectZero, [inputView firstRectForRange:multiRectRange]));
1795- (void)testFirstRectForRangeReturnsCorrectRectWithOverlappingRectsExceedingThresholdRightToLeft {
1799 [inputView setSelectionRects:@[
1810 if (@available(iOS 17, *)) {
1811 XCTAssertTrue(CGRectEqualToRect(CGRectMake(0, 0, 300, 100),
1812 [inputView firstRectForRange:multiRectRange]));
1814 XCTAssertTrue(CGRectEqualToRect(CGRectZero, [inputView firstRectForRange:multiRectRange]));
1818- (void)testFirstRectForRangeReturnsCorrectRectWithOverlappingRectsWithinThresholdLeftToRight {
1822 [inputView setSelectionRects:@[
1833 if (@available(iOS 17, *)) {
1834 XCTAssertTrue(CGRectEqualToRect(CGRectMake(100, 0, 400, 140),
1835 [inputView firstRectForRange:multiRectRange]));
1837 XCTAssertTrue(CGRectEqualToRect(CGRectZero, [inputView firstRectForRange:multiRectRange]));
1841- (void)testFirstRectForRangeReturnsCorrectRectWithOverlappingRectsWithinThresholdRightToLeft {
1845 [inputView setSelectionRects:@[
1856 if (@available(iOS 17, *)) {
1857 XCTAssertTrue(CGRectEqualToRect(CGRectMake(0, 0, 400, 140),
1858 [inputView firstRectForRange:multiRectRange]));
1860 XCTAssertTrue(CGRectEqualToRect(CGRectZero, [inputView firstRectForRange:multiRectRange]));
1864- (void)testClosestPositionToPoint {
1869 [inputView setSelectionRects:@[
1874 CGPoint point = CGPointMake(150, 150);
1875 XCTAssertEqual(2U, ((
FlutterTextPosition*)[inputView closestPositionToPoint:point]).index);
1876 XCTAssertEqual(UITextStorageDirectionBackward,
1881 [inputView setSelectionRects:@[
1888 point = CGPointMake(125, 150);
1889 XCTAssertEqual(2U, ((
FlutterTextPosition*)[inputView closestPositionToPoint:point]).index);
1890 XCTAssertEqual(UITextStorageDirectionForward,
1895 [inputView setSelectionRects:@[
1902 point = CGPointMake(125, 201);
1903 XCTAssertEqual(4U, ((
FlutterTextPosition*)[inputView closestPositionToPoint:point]).index);
1904 XCTAssertEqual(UITextStorageDirectionBackward,
1908 [inputView setSelectionRects:@[
1914 point = CGPointMake(125, 250);
1915 XCTAssertEqual(4U, ((
FlutterTextPosition*)[inputView closestPositionToPoint:point]).index);
1916 XCTAssertEqual(UITextStorageDirectionBackward,
1920 [inputView setSelectionRects:@[
1925 point = CGPointMake(110, 50);
1926 XCTAssertEqual(2U, ((
FlutterTextPosition*)[inputView closestPositionToPoint:point]).index);
1927 XCTAssertEqual(UITextStorageDirectionForward,
1933 XCTAssertEqual(1U, ((
FlutterTextPosition*)[inputView closestPositionToPoint:point]).index);
1934 XCTAssertEqual(UITextStorageDirectionForward,
1939- (void)testClosestPositionToPointRTL {
1943 [inputView setSelectionRects:@[
1959 XCTAssertEqual(0U, position.
index);
1960 XCTAssertEqual(UITextStorageDirectionForward, position.
affinity);
1962 XCTAssertEqual(1U, position.
index);
1963 XCTAssertEqual(UITextStorageDirectionBackward, position.
affinity);
1965 XCTAssertEqual(1U, position.
index);
1966 XCTAssertEqual(UITextStorageDirectionForward, position.
affinity);
1968 XCTAssertEqual(2U, position.
index);
1969 XCTAssertEqual(UITextStorageDirectionBackward, position.
affinity);
1971 XCTAssertEqual(2U, position.
index);
1972 XCTAssertEqual(UITextStorageDirectionForward, position.
affinity);
1974 XCTAssertEqual(3U, position.
index);
1975 XCTAssertEqual(UITextStorageDirectionBackward, position.
affinity);
1977 XCTAssertEqual(3U, position.
index);
1978 XCTAssertEqual(UITextStorageDirectionBackward, position.
affinity);
1981- (void)testSelectionRectsForRange {
1985 CGRect testRect0 = CGRectMake(100, 100, 100, 100);
1986 CGRect
testRect1 = CGRectMake(200, 200, 100, 100);
1987 [inputView setSelectionRects:@[
1996 XCTAssertTrue(CGRectEqualToRect(testRect0, [inputView selectionRectsForRange:range][0].
rect));
1997 XCTAssertTrue(CGRectEqualToRect(
testRect1, [inputView selectionRectsForRange:range][1].
rect));
1998 XCTAssertEqual(2U, [[inputView selectionRectsForRange:range]
count]);
2002 XCTAssertEqual(1U, [[inputView selectionRectsForRange:range]
count]);
2003 XCTAssertTrue(CGRectEqualToRect(
2004 CGRectMake(testRect0.origin.x, testRect0.origin.y, 0, testRect0.size.height),
2005 [inputView selectionRectsForRange:range][0].
rect));
2008- (void)testClosestPositionToPointWithinRange {
2013 [inputView setSelectionRects:@[
2020 CGPoint point = CGPointMake(125, 150);
2023 3U, ((
FlutterTextPosition*)[inputView closestPositionToPoint:point withinRange:range]).index);
2025 UITextStorageDirectionForward,
2026 ((
FlutterTextPosition*)[inputView closestPositionToPoint:point withinRange:range]).affinity);
2029 [inputView setSelectionRects:@[
2036 point = CGPointMake(125, 150);
2039 1U, ((
FlutterTextPosition*)[inputView closestPositionToPoint:point withinRange:range]).index);
2041 UITextStorageDirectionForward,
2042 ((
FlutterTextPosition*)[inputView closestPositionToPoint:point withinRange:range]).affinity);
2045- (void)testClosestPositionToPointWithPartialSelectionRects {
2054 XCTAssertTrue(CGRectEqualToRect(
2057 affinity:UITextStorageDirectionForward]],
2058 CGRectMake(100, 0, 0, 100)));
2061 XCTAssertTrue(CGRectEqualToRect(
2064 affinity:UITextStorageDirectionForward]],
2068#pragma mark - Floating Cursor - Tests
2070- (void)testFloatingCursorDoesNotThrow {
2080- (void)testFloatingCursor {
2084 @"selectionBase" : @1,
2085 @"selectionExtent" : @1,
2096 [inputView setSelectionRects:@[ first, second, third, fourth ]];
2099 XCTAssertTrue(CGRectEqualToRect(
2102 affinity:UITextStorageDirectionForward]],
2103 CGRectMake(0, 0, 0, 100)));
2106 XCTAssertTrue(CGRectEqualToRect(
2109 affinity:UITextStorageDirectionForward]],
2110 CGRectMake(100, 100, 0, 100)));
2111 XCTAssertTrue(CGRectEqualToRect(
2114 affinity:UITextStorageDirectionForward]],
2115 CGRectMake(200, 200, 0, 100)));
2116 XCTAssertTrue(CGRectEqualToRect(
2119 affinity:UITextStorageDirectionForward]],
2120 CGRectMake(300, 300, 0, 100)));
2123 XCTAssertTrue(CGRectEqualToRect(
2126 affinity:UITextStorageDirectionForward]],
2127 CGRectMake(400, 300, 0, 100)));
2129 XCTAssertTrue(CGRectEqualToRect(
2132 affinity:UITextStorageDirectionForward]],
2138 @"selectionBase" : @2,
2139 @"selectionExtent" : @2,
2142 XCTAssertTrue(CGRectEqualToRect(
2145 affinity:UITextStorageDirectionBackward]],
2146 CGRectMake(0, 0, 0, 100)));
2149 XCTAssertTrue(CGRectEqualToRect(
2152 affinity:UITextStorageDirectionBackward]],
2153 CGRectMake(100, 0, 0, 100)));
2154 XCTAssertTrue(CGRectEqualToRect(
2157 affinity:UITextStorageDirectionBackward]],
2158 CGRectMake(200, 100, 0, 100)));
2159 XCTAssertTrue(CGRectEqualToRect(
2162 affinity:UITextStorageDirectionBackward]],
2163 CGRectMake(300, 200, 0, 100)));
2164 XCTAssertTrue(CGRectEqualToRect(
2167 affinity:UITextStorageDirectionBackward]],
2168 CGRectMake(400, 300, 0, 100)));
2170 XCTAssertTrue(CGRectEqualToRect(
2173 affinity:UITextStorageDirectionBackward]],
2178 CGRect initialBounds = inputView.bounds;
2180 XCTAssertTrue(CGRectEqualToRect(initialBounds, inputView.bounds));
2181 OCMVerify([
engine flutterTextInputView:inputView
2182 updateFloatingCursor:FlutterFloatingCursorDragStateStart
2184 withPosition:[OCMArg checkWithBlock:^
BOOL(NSDictionary*
state) {
2185 return ([
state[
@"X"] isEqualToNumber:@(0)]) &&
2186 ([
state[
@"Y"] isEqualToNumber:@(0)]);
2190 XCTAssertTrue(CGRectEqualToRect(initialBounds, inputView.bounds));
2191 OCMVerify([
engine flutterTextInputView:inputView
2192 updateFloatingCursor:FlutterFloatingCursorDragStateUpdate
2194 withPosition:[OCMArg checkWithBlock:^
BOOL(NSDictionary*
state) {
2195 return ([
state[
@"X"] isEqualToNumber:@(333)]) &&
2196 ([
state[
@"Y"] isEqualToNumber:@(333)]);
2200 XCTAssertTrue(CGRectEqualToRect(initialBounds, inputView.bounds));
2201 OCMVerify([
engine flutterTextInputView:inputView
2202 updateFloatingCursor:FlutterFloatingCursorDragStateEnd
2204 withPosition:[OCMArg checkWithBlock:^
BOOL(NSDictionary*
state) {
2205 return ([
state[
@"X"] isEqualToNumber:@(0)]) &&
2206 ([
state[
@"Y"] isEqualToNumber:@(0)]);
2210#pragma mark - UIKeyInput Overrides - Tests
2212- (void)testInsertTextAddsPlaceholderSelectionRects {
2215 setTextInputState:@{@"text" : @"test", @"selectionBase" : @1, @"selectionExtent" : @1}];
2225 [inputView setSelectionRects:@[ first, second, third, fourth ]];
2256#pragma mark - Autofill - Utilities
2258- (NSMutableDictionary*)mutablePasswordTemplateCopy {
2261 @"inputType" : @{
@"name" :
@"TextInuptType.text"},
2262 @"keyboardAppearance" :
@"Brightness.light",
2263 @"obscureText" : @YES,
2264 @"inputAction" :
@"TextInputAction.unspecified",
2265 @"smartDashesType" :
@"0",
2266 @"smartQuotesType" :
@"0",
2267 @"autocorrect" : @YES
2271 return [_passwordTemplate mutableCopy];
2275 return [
self.installedInputViews
2276 filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"isVisibleToAutofill == YES"]];
2279- (void)commitAutofillContextAndVerify {
2283 [textInputPlugin handleMethodCall:methodCall
2284 result:^(id _Nullable result){
2287 XCTAssertEqual(
self.viewsVisibleToAutofill.count,
2292 XCTAssertEqual(
self.installedInputViews.count, 1ul);