5#import "flutter/shell/platform/darwin/common/framework/Source/FlutterBinaryMessengerRelay.h"
6#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterEngine_Test.h"
7#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h"
9#import <OCMock/OCMock.h>
10#import <XCTest/XCTest.h>
12#import "flutter/shell/platform/darwin/common/framework/Headers/FlutterMacros.h"
13#import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterEngine.h"
14#import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterViewController.h"
15#import "flutter/shell/platform/darwin/ios/framework/Source/UIViewController+FlutterScreenAndSceneIfLoaded.h"
25- (void)setEditableTransform:(NSArray*)matrix;
26- (void)setTextInputClient:(
int)client;
27- (void)setTextInputState:(NSDictionary*)state;
28- (void)setMarkedRect:(CGRect)markedRect;
32- (void)configureWithDictionary:(NSDictionary*)configuration;
40- (void)postAccessibilityNotification:(UIAccessibilityNotifications)notification target:(
id)target;
47- (void)postAccessibilityNotification:(UIAccessibilityNotifications)notification target:(
id)target {
48 self.receivedNotification = notification;
52- (
BOOL)accessibilityElementIsFocused {
53 return _isAccessibilityFocused;
59@property(nonatomic, strong) UITextField*
textField;
69@property(nonatomic, readonly)
72- (void)cleanUpViewHierarchy:(
BOOL)includeActiveView
73 clearText:(
BOOL)clearText
74 delayRemoval:(
BOOL)delayRemoval;
87 NSDictionary* _template;
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(0
U, 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);
2296#pragma mark - Autofill - Tests
2298- (void)testDisablingAutofillOnInputClient {
2299 NSDictionary* config =
self.mutableTemplateCopy;
2300 [config setValue:@"YES" forKey:@"obscureText"];
2302 [
self setClientId:123 configuration:config];
2308- (void)testAutofillEnabledByDefault {
2309 NSDictionary* config =
self.mutableTemplateCopy;
2310 [config setValue:@"NO" forKey:@"obscureText"];
2311 [config setValue:@{@"uniqueIdentifier" : @"field1", @"editingValue" : @{@"text" : @""}}
2312 forKey:@"autofill"];
2314 [
self setClientId:123 configuration:config];
2320- (void)testAutofillContext {
2321 NSMutableDictionary* field1 =
self.mutableTemplateCopy;
2324 @"uniqueIdentifier" : @"field1",
2325 @"hints" : @[ @"hint1" ],
2326 @"editingValue" : @{@"text" : @""}
2328 forKey:@"autofill"];
2330 NSMutableDictionary* field2 =
self.mutablePasswordTemplateCopy;
2332 @"uniqueIdentifier" : @"field2",
2333 @"hints" : @[ @"hint2" ],
2334 @"editingValue" : @{@"text" : @""}
2336 forKey:@"autofill"];
2338 NSMutableDictionary* config = [field1 mutableCopy];
2339 [config setValue:@[ field1, field2 ] forKey:@"fields"];
2341 [
self setClientId:123 configuration:config];
2342 XCTAssertEqual(
self.viewsVisibleToAutofill.count, 2ul);
2346 [textInputPlugin cleanUpViewHierarchy:NO clearText:YES delayRemoval:NO];
2347 XCTAssertEqual(
self.installedInputViews.count, 2ul);
2349 [
self ensureOnlyActiveViewCanBecomeFirstResponder];
2352 NSMutableDictionary* field3 =
self.mutablePasswordTemplateCopy;
2354 @"uniqueIdentifier" : @"field3",
2355 @"hints" : @[ @"hint3" ],
2356 @"editingValue" : @{@"text" : @""}
2358 forKey:@"autofill"];
2362 [config setValue:@[ field1, field3 ] forKey:@"fields"];
2364 [
self setClientId:123 configuration:config];
2366 XCTAssertEqual(
self.viewsVisibleToAutofill.count, 2ul);
2369 [textInputPlugin cleanUpViewHierarchy:NO clearText:YES delayRemoval:NO];
2370 XCTAssertEqual(
self.installedInputViews.count, 3ul);
2372 [
self ensureOnlyActiveViewCanBecomeFirstResponder];
2375 for (NSString*
key in oldContext.allKeys) {
2380 config =
self.mutablePasswordTemplateCopy;
2383 [
self setClientId:124 configuration:config];
2384 [
self ensureOnlyActiveViewCanBecomeFirstResponder];
2386 XCTAssertEqual(
self.viewsVisibleToAutofill.count, 1ul);
2389 [textInputPlugin cleanUpViewHierarchy:NO clearText:YES delayRemoval:NO];
2390 XCTAssertEqual(
self.installedInputViews.count, 4ul);
2393 for (NSString*
key in oldContext.allKeys) {
2398 [
self ensureOnlyActiveViewCanBecomeFirstResponder];
2402 [
self setClientId:200 configuration:config];
2405 XCTAssertEqual(
self.viewsVisibleToAutofill.count, 1ul);
2408 [textInputPlugin cleanUpViewHierarchy:NO clearText:YES delayRemoval:NO];
2409 XCTAssertEqual(
self.installedInputViews.count, 4ul);
2412 for (NSString*
key in oldContext.allKeys) {
2416 [
self ensureOnlyActiveViewCanBecomeFirstResponder];
2419- (void)testCommitAutofillContext {
2420 NSMutableDictionary* field1 =
self.mutableTemplateCopy;
2422 @"uniqueIdentifier" : @"field1",
2423 @"hints" : @[ @"hint1" ],
2424 @"editingValue" : @{@"text" : @""}
2426 forKey:@"autofill"];
2428 NSMutableDictionary* field2 =
self.mutablePasswordTemplateCopy;
2430 @"uniqueIdentifier" : @"field2",
2431 @"hints" : @[ @"hint2" ],
2432 @"editingValue" : @{@"text" : @""}
2434 forKey:@"autofill"];
2436 NSMutableDictionary* field3 =
self.mutableTemplateCopy;
2438 @"uniqueIdentifier" : @"field3",
2439 @"hints" : @[ @"hint3" ],
2440 @"editingValue" : @{@"text" : @""}
2442 forKey:@"autofill"];
2444 NSMutableDictionary* config = [field1 mutableCopy];
2445 [config setValue:@[ field1, field2 ] forKey:@"fields"];
2447 [
self setClientId:123 configuration:config];
2448 XCTAssertEqual(
self.viewsVisibleToAutofill.count, 2ul);
2450 [
self ensureOnlyActiveViewCanBecomeFirstResponder];
2452 [
self commitAutofillContextAndVerify];
2453 [
self ensureOnlyActiveViewCanBecomeFirstResponder];
2456 [
self setClientId:123 configuration:config];
2458 [
self setClientId:124 configuration:field3];
2459 XCTAssertEqual(
self.viewsVisibleToAutofill.count, 1ul);
2461 [textInputPlugin cleanUpViewHierarchy:NO clearText:YES delayRemoval:NO];
2462 XCTAssertEqual(
self.installedInputViews.count, 3ul);
2465 [
self ensureOnlyActiveViewCanBecomeFirstResponder];
2467 [
self commitAutofillContextAndVerify];
2468 [
self ensureOnlyActiveViewCanBecomeFirstResponder];
2471 [
self setClientId:125 configuration:self.mutableTemplateCopy];
2473 XCTAssertEqual(
self.viewsVisibleToAutofill.count, 0ul);
2477 [textInputPlugin cleanUpViewHierarchy:NO clearText:YES delayRemoval:NO];
2478 XCTAssertEqual(
self.installedInputViews.count, 1ul);
2480 [
self ensureOnlyActiveViewCanBecomeFirstResponder];
2482 [
self commitAutofillContextAndVerify];
2483 [
self ensureOnlyActiveViewCanBecomeFirstResponder];
2486- (void)testAutofillInputViews {
2487 NSMutableDictionary* field1 =
self.mutableTemplateCopy;
2489 @"uniqueIdentifier" : @"field1",
2490 @"hints" : @[ @"hint1" ],
2491 @"editingValue" : @{@"text" : @""}
2493 forKey:@"autofill"];
2495 NSMutableDictionary* field2 =
self.mutablePasswordTemplateCopy;
2497 @"uniqueIdentifier" : @"field2",
2498 @"hints" : @[ @"hint2" ],
2499 @"editingValue" : @{@"text" : @""}
2501 forKey:@"autofill"];
2503 NSMutableDictionary* config = [field1 mutableCopy];
2504 [config setValue:@[ field1, field2 ] forKey:@"fields"];
2506 [
self setClientId:123 configuration:config];
2507 [
self ensureOnlyActiveViewCanBecomeFirstResponder];
2510 NSArray<FlutterTextInputView*>* inputFields =
self.installedInputViews;
2513 XCTAssertEqual(inputFields.count, 2ul);
2514 XCTAssertEqual(
self.viewsVisibleToAutofill.count, 2ul);
2520 [
self ensureOnlyActiveViewCanBecomeFirstResponder];
2523 OCMVerify([
engine flutterTextInputView:inactiveView
2524 updateEditingClient:0
2525 withState:[OCMArg isNotNil]
2526 withTag:
@"field2"]);
2529- (void)testPasswordAutofillHack {
2530 NSDictionary* config =
self.mutableTemplateCopy;
2531 [config setValue:@"YES" forKey:@"obscureText"];
2532 [
self setClientId:123 configuration:config];
2535 NSArray<FlutterTextInputView*>* inputFields =
self.installedInputViews;
2539 XCTAssert([inputView isKindOfClass:[UITextField
class]]);
2542 XCTAssertNotEqual([inputView performSelector:
@selector(
font)], nil);
2545- (void)testClearAutofillContextClearsSelection {
2546 NSMutableDictionary* regularField =
self.mutableTemplateCopy;
2547 NSDictionary* editingValue = @{
2548 @"text" :
@"REGULAR_TEXT_FIELD",
2549 @"composingBase" : @0,
2550 @"composingExtent" : @3,
2551 @"selectionBase" : @1,
2552 @"selectionExtent" : @4
2554 [regularField setValue:@{
2555 @"uniqueIdentifier" : @"field2",
2556 @"hints" : @[ @"hint2" ],
2557 @"editingValue" : editingValue,
2559 forKey:@"autofill"];
2560 [regularField addEntriesFromDictionary:editingValue];
2561 [
self setClientId:123 configuration:regularField];
2562 [
self ensureOnlyActiveViewCanBecomeFirstResponder];
2563 XCTAssertEqual(
self.installedInputViews.count, 1ul);
2566 XCTAssert([oldInputView.text isEqualToString:
@"REGULAR_TEXT_FIELD"]);
2568 XCTAssert(NSEqualRanges(selectionRange.
range, NSMakeRange(1, 3)));
2572 [
self setClientId:124 configuration:self.mutablePasswordTemplateCopy];
2573 [
self ensureOnlyActiveViewCanBecomeFirstResponder];
2575 XCTAssertEqual(
self.installedInputViews.count, 2ul);
2577 [textInputPlugin cleanUpViewHierarchy:NO clearText:YES delayRemoval:NO];
2578 XCTAssertEqual(
self.installedInputViews.count, 1ul);
2581 XCTAssert([oldInputView.text isEqualToString:
@""]);
2583 XCTAssert(NSEqualRanges(selectionRange.
range, NSMakeRange(0, 0)));
2586- (void)testGarbageInputViewsAreNotRemovedImmediately {
2588 [
self setClientId:123 configuration:self.mutablePasswordTemplateCopy];
2589 [
self ensureOnlyActiveViewCanBecomeFirstResponder];
2591 XCTAssertEqual(
self.installedInputViews.count, 1ul);
2594 [
self setClientId:124 configuration:self.mutableTemplateCopy];
2595 [
self ensureOnlyActiveViewCanBecomeFirstResponder];
2597 XCTAssertEqual(
self.installedInputViews.count, 2ul);
2599 [
self commitAutofillContextAndVerify];
2602- (void)testScribbleSetSelectionRects {
2603 NSMutableDictionary* regularField =
self.mutableTemplateCopy;
2604 NSDictionary* editingValue = @{
2605 @"text" :
@"REGULAR_TEXT_FIELD",
2606 @"composingBase" : @0,
2607 @"composingExtent" : @3,
2608 @"selectionBase" : @1,
2609 @"selectionExtent" : @4
2611 [regularField setValue:@{
2612 @"uniqueIdentifier" : @"field1",
2613 @"hints" : @[ @"hint2" ],
2614 @"editingValue" : editingValue,
2616 forKey:@"autofill"];
2617 [regularField addEntriesFromDictionary:editingValue];
2618 [
self setClientId:123 configuration:regularField];
2619 XCTAssertEqual(
self.installedInputViews.count, 1ul);
2622 NSArray<NSNumber*>* selectionRect = [NSArray arrayWithObjects:@0, @0, @100, @100, @0, @1, nil];
2623 NSArray*
selectionRects = [NSArray arrayWithObjects:selectionRect, nil];
2627 [textInputPlugin handleMethodCall:methodCall
2628 result:^(id _Nullable result){
2634- (void)testDecommissionedViewAreNotReusedByAutofill {
2636 NSMutableDictionary* configuration =
self.mutableTemplateCopy;
2637 [configuration setValue:@{
2638 @"uniqueIdentifier" : @"field1",
2639 @"hints" : @[ UITextContentTypePassword ],
2640 @"editingValue" : @{@"text" : @""}
2642 forKey:@"autofill"];
2643 [configuration setValue:@[ [configuration copy] ] forKey:@"fields"];
2645 [
self setClientId:123 configuration:configuration];
2647 [
self setTextInputHide];
2650 [
self setClientId:124 configuration:configuration];
2654 XCTAssertNotNil(previousActiveView);
2658- (void)testInitialActiveViewCantAccessTextInputDelegate {
2665#pragma mark - Accessibility - Tests
2667- (void)testUITextInputAccessibilityNotHiddenWhenShowed {
2668 [
self setClientId:123 configuration:self.mutableTemplateCopy];
2671 [
self setTextInputShow];
2673 NSArray<FlutterTextInputView*>* inputFields =
self.installedInputViews;
2676 XCTAssertEqual([inputFields
count], 1u);
2679 [
self setTextInputHide];
2681 inputFields =
self.installedInputViews;
2684 XCTAssertEqual([inputFields
count], 0u);
2687- (void)testFlutterTextInputViewDirectFocusToBackingTextInput {
2690 UIView* container = [[UIView alloc] init];
2691 UIAccessibilityElement* backing =
2692 [[UIAccessibilityElement alloc] initWithAccessibilityContainer:container];
2693 inputView.backingTextInputAccessibilityObject = backing;
2695 inputView.isAccessibilityFocused = YES;
2698 XCTAssertEqual(inputView.receivedNotification, UIAccessibilityScreenChangedNotification);
2699 XCTAssertEqual(inputView.receivedNotificationTarget, backing);
2702- (void)testFlutterTokenizerCanParseLines {
2704 id<UITextInputTokenizer> tokenizer = [inputView
tokenizer];
2707 FlutterTextRange* range = [
self getLineRangeFromTokenizer:tokenizer atIndex:0];
2708 XCTAssertEqual(range.
range.location, 0u);
2709 XCTAssertEqual(range.
range.length, 0u);
2711 [inputView
insertText:@"how are you\nI am fine, Thank you"];
2713 range = [
self getLineRangeFromTokenizer:tokenizer atIndex:0];
2714 XCTAssertEqual(range.
range.location, 0u);
2715 XCTAssertEqual(range.
range.length, 11u);
2717 range = [
self getLineRangeFromTokenizer:tokenizer atIndex:2];
2718 XCTAssertEqual(range.
range.location, 0u);
2719 XCTAssertEqual(range.
range.length, 11u);
2721 range = [
self getLineRangeFromTokenizer:tokenizer atIndex:11];
2722 XCTAssertEqual(range.
range.location, 0u);
2723 XCTAssertEqual(range.
range.length, 11u);
2725 range = [
self getLineRangeFromTokenizer:tokenizer atIndex:12];
2726 XCTAssertEqual(range.
range.location, 12u);
2727 XCTAssertEqual(range.
range.length, 20u);
2729 range = [
self getLineRangeFromTokenizer:tokenizer atIndex:15];
2730 XCTAssertEqual(range.
range.location, 12u);
2731 XCTAssertEqual(range.
range.length, 20u);
2733 range = [
self getLineRangeFromTokenizer:tokenizer atIndex:32];
2734 XCTAssertEqual(range.
range.location, 12u);
2735 XCTAssertEqual(range.
range.length, 20u);
2738- (void)testFlutterTokenizerLineEnclosingEndOfDocumentInBackwardDirectionShouldNotReturnNil {
2740 [inputView
insertText:@"0123456789\n012345"];
2741 id<UITextInputTokenizer> tokenizer = [inputView
tokenizer];
2744 (
FlutterTextRange*)[tokenizer rangeEnclosingPosition:[inputView endOfDocument]
2745 withGranularity:UITextGranularityLine
2746 inDirection:UITextStorageDirectionBackward];
2747 XCTAssertEqual(range.
range.location, 11u);
2748 XCTAssertEqual(range.
range.length, 6u);
2751- (void)testFlutterTokenizerLineEnclosingEndOfDocumentInForwardDirectionShouldReturnNilOnIOS17 {
2753 [inputView
insertText:@"0123456789\n012345"];
2754 id<UITextInputTokenizer> tokenizer = [inputView
tokenizer];
2757 (
FlutterTextRange*)[tokenizer rangeEnclosingPosition:[inputView endOfDocument]
2758 withGranularity:UITextGranularityLine
2759 inDirection:UITextStorageDirectionForward];
2760 if (@available(iOS 17.0, *)) {
2761 XCTAssertNil(range);
2763 XCTAssertEqual(range.
range.location, 11u);
2764 XCTAssertEqual(range.
range.length, 6u);
2768- (void)testFlutterTokenizerLineEnclosingOutOfRangePositionShouldReturnNilOnIOS17 {
2770 [inputView
insertText:@"0123456789\n012345"];
2771 id<UITextInputTokenizer> tokenizer = [inputView
tokenizer];
2776 withGranularity:UITextGranularityLine
2777 inDirection:UITextStorageDirectionForward];
2778 if (@available(iOS 17.0, *)) {
2779 XCTAssertNil(range);
2781 XCTAssertEqual(range.
range.location, 0u);
2782 XCTAssertEqual(range.
range.length, 0u);
2786- (void)testFlutterTextInputPluginRetainsFlutterTextInputView {
2791 __weak UIView* activeView;
2796 [NSNumber numberWithInt:123], self.mutablePasswordTemplateCopy
2799 result:^(id _Nullable result){
2805 result:^(id _Nullable result){
2807 XCTAssertNotNil(activeView);
2810 XCTAssertNotNil(activeView);
2813- (void)testFlutterTextInputPluginHostViewNilCrash {
2816 XCTAssertThrows([myInputPlugin hostView],
@"Throws exception if host view is nil");
2819- (void)testFlutterTextInputPluginHostViewNotNil {
2828- (void)testSetPlatformViewClient {
2835 arguments:@[ [NSNumber numberWithInt:123], self.mutablePasswordTemplateCopy ]];
2837 result:^(id _Nullable result){
2840 XCTAssertNotNil(activeView.superview,
@"activeView must be added to the view hierarchy.");
2843 arguments:@{@"platformViewId" : [NSNumber numberWithLong:456]}];
2845 result:^(id _Nullable result){
2847 XCTAssertNil(activeView.superview,
@"activeView must be removed from view hierarchy.");
2850- (void)testEditMenu_shouldSetupEditMenuDelegateCorrectly {
2851 if (@available(iOS 16.0, *)) {
2853 [UIApplication.sharedApplication.keyWindow addSubview:inputView];
2854 XCTAssertEqual(inputView.editMenuInteraction.delegate, inputView,
2855 @"editMenuInteraction setup delegate correctly");
2859- (void)testEditMenu_shouldNotPresentEditMenuIfNotFirstResponder {
2860 if (@available(iOS 16.0, *)) {
2864 XCTAssertFalse(shownEditMenu,
@"Should not show edit menu if not first responder.");
2868- (void)testEditMenu_shouldPresentEditMenuWithCorrectConfiguration {
2869 if (@available(iOS 16.0, *)) {
2877 arguments:@[ @(123),
self.mutableTemplateCopy ]];
2879 result:^(id _Nullable result){
2885 OCMStub([mockInputView isFirstResponder]).andReturn(YES);
2887 XCTestExpectation*
expectation = [[XCTestExpectation alloc]
2888 initWithDescription:@"presentEditMenuWithConfiguration must be called."];
2890 id mockInteraction = OCMClassMock([UIEditMenuInteraction
class]);
2891 OCMStub([mockInputView editMenuInteraction]).andReturn(mockInteraction);
2892 OCMStub([mockInteraction presentEditMenuWithConfiguration:[OCMArg
any]])
2893 .andDo(^(NSInvocation* invocation) {
2895 [invocation retainArguments];
2896 UIEditMenuConfiguration* config;
2897 [invocation getArgument:&config atIndex:2];
2898 XCTAssertEqual(config.preferredArrowDirection, UIEditMenuArrowDirectionAutomatic,
2899 @"UIEditMenuConfiguration must use automatic arrow direction.");
2900 XCTAssert(CGPointEqualToPoint(config.sourcePoint, CGPointZero),
2901 @"UIEditMenuConfiguration must have the correct point.");
2902 [expectation fulfill];
2905 NSDictionary<NSString*, NSNumber*>* encodedTargetRect =
2906 @{
@"x" : @(0),
@"y" : @(0),
@"width" : @(0),
@"height" : @(0)};
2908 BOOL shownEditMenu = [myInputPlugin
showEditMenu:@{@"targetRect" : encodedTargetRect}];
2909 XCTAssertTrue(shownEditMenu,
@"Should show edit menu with correct configuration.");
2910 [
self waitForExpectations:@[ expectation ] timeout:1.0];
2914- (void)testEditMenu_shouldPresentEditMenuWithCorectTargetRect {
2915 if (@available(iOS 16.0, *)) {
2924 arguments:@[ @(123),
self.mutableTemplateCopy ]];
2926 result:^(id _Nullable result){
2932 OCMStub([mockInputView isFirstResponder]).andReturn(YES);
2934 XCTestExpectation*
expectation = [[XCTestExpectation alloc]
2935 initWithDescription:@"presentEditMenuWithConfiguration must be called."];
2937 id mockInteraction = OCMClassMock([UIEditMenuInteraction
class]);
2938 OCMStub([mockInputView editMenuInteraction]).andReturn(mockInteraction);
2939 OCMStub([mockInteraction presentEditMenuWithConfiguration:[OCMArg
any]])
2940 .andDo(^(NSInvocation* invocation) {
2941 [expectation fulfill];
2944 myInputView.frame = CGRectMake(10, 20, 30, 40);
2945 NSDictionary<NSString*, NSNumber*>* encodedTargetRect =
2946 @{
@"x" : @(100),
@"y" : @(200),
@"width" : @(300),
@"height" : @(400)};
2948 BOOL shownEditMenu = [myInputPlugin
showEditMenu:@{@"targetRect" : encodedTargetRect}];
2949 XCTAssertTrue(shownEditMenu,
@"Should show edit menu with correct configuration.");
2950 [
self waitForExpectations:@[ expectation ] timeout:1.0];
2956 XCTAssert(CGRectEqualToRect(targetRect, CGRectMake(90, 180, 300, 400)),
2957 @"targetRectForConfiguration must return the correct target rect.");
2961- (void)testInteractiveKeyboardAfterUserScrollWillResignFirstResponder {
2963 [UIApplication.sharedApplication.keyWindow addSubview:inputView];
2966 [inputView reloadInputViews];
2967 [inputView becomeFirstResponder];
2968 XCTAssert(inputView.isFirstResponder);
2970 CGRect keyboardFrame = CGRectMake(0, 500, 500, 500);
2971 [NSNotificationCenter.defaultCenter
2972 postNotificationName:UIKeyboardWillShowNotification
2974 userInfo:@{UIKeyboardFrameEndUserInfoKey : @(keyboardFrame)}];
2978 [textInputPlugin handleMethodCall:onPointerMoveCall
2979 result:^(id _Nullable result){
2981 XCTAssertFalse(inputView.isFirstResponder);
2985- (void)testInteractiveKeyboardAfterUserScrollToTopOfKeyboardWillTakeScreenshot {
2986 NSSet<UIScene*>* scenes = UIApplication.sharedApplication.connectedScenes;
2987 XCTAssertEqual(scenes.count, 1UL,
@"There must only be 1 scene for test");
2988 UIScene* scene = scenes.anyObject;
2989 XCTAssert([scene isKindOfClass:[UIWindowScene
class]],
@"Must be a window scene for test");
2990 UIWindowScene* windowScene = (UIWindowScene*)scene;
2991 XCTAssert(windowScene.windows.count > 0,
@"There must be at least 1 window for test");
2992 UIWindow*
window = windowScene.windows[0];
2993 [window addSubview:viewController.view];
2995 [viewController loadView];
2998 [UIApplication.sharedApplication.keyWindow addSubview:inputView];
3001 [inputView reloadInputViews];
3002 [inputView becomeFirstResponder];
3006 [subView removeFromSuperview];
3010 CGRect keyboardFrame = CGRectMake(0, 500, 500, 500);
3011 [NSNotificationCenter.defaultCenter
3012 postNotificationName:UIKeyboardWillShowNotification
3014 userInfo:@{UIKeyboardFrameEndUserInfoKey : @(keyboardFrame)}];
3018 [textInputPlugin handleMethodCall:onPointerMoveCall
3019 result:^(id _Nullable result){
3023 [subView removeFromSuperview];
3028- (void)testInteractiveKeyboardScreenshotWillBeMovedDownAfterUserScroll {
3029 NSSet<UIScene*>* scenes = UIApplication.sharedApplication.connectedScenes;
3030 XCTAssertEqual(scenes.count, 1UL,
@"There must only be 1 scene for test");
3031 UIScene* scene = scenes.anyObject;
3032 XCTAssert([scene isKindOfClass:[UIWindowScene
class]],
@"Must be a window scene for test");
3033 UIWindowScene* windowScene = (UIWindowScene*)scene;
3034 XCTAssert(windowScene.windows.count > 0,
@"There must be at least 1 window for test");
3035 UIWindow*
window = windowScene.windows[0];
3036 [window addSubview:viewController.view];
3038 [viewController loadView];
3041 [UIApplication.sharedApplication.keyWindow addSubview:inputView];
3044 [inputView reloadInputViews];
3045 [inputView becomeFirstResponder];
3047 CGRect keyboardFrame = CGRectMake(0, 500, 500, 500);
3048 [NSNotificationCenter.defaultCenter
3049 postNotificationName:UIKeyboardWillShowNotification
3051 userInfo:@{UIKeyboardFrameEndUserInfoKey : @(keyboardFrame)}];
3055 [textInputPlugin handleMethodCall:onPointerMoveCall
3056 result:^(id _Nullable result){
3065 [textInputPlugin handleMethodCall:onPointerMoveCallMove
3066 result:^(id _Nullable result){
3073 [subView removeFromSuperview];
3078- (void)testInteractiveKeyboardScreenshotWillBeMovedToOrginalPositionAfterUserScroll {
3079 NSSet<UIScene*>* scenes = UIApplication.sharedApplication.connectedScenes;
3080 XCTAssertEqual(scenes.count, 1UL,
@"There must only be 1 scene for test");
3081 UIScene* scene = scenes.anyObject;
3082 XCTAssert([scene isKindOfClass:[UIWindowScene
class]],
@"Must be a window scene for test");
3083 UIWindowScene* windowScene = (UIWindowScene*)scene;
3084 XCTAssert(windowScene.windows.count > 0,
@"There must be at least 1 window for test");
3085 UIWindow*
window = windowScene.windows[0];
3086 [window addSubview:viewController.view];
3088 [viewController loadView];
3091 [UIApplication.sharedApplication.keyWindow addSubview:inputView];
3094 [inputView reloadInputViews];
3095 [inputView becomeFirstResponder];
3097 CGRect keyboardFrame = CGRectMake(0, 500, 500, 500);
3098 [NSNotificationCenter.defaultCenter
3099 postNotificationName:UIKeyboardWillShowNotification
3101 userInfo:@{UIKeyboardFrameEndUserInfoKey : @(keyboardFrame)}];
3105 [textInputPlugin handleMethodCall:onPointerMoveCall
3106 result:^(id _Nullable result){
3114 [textInputPlugin handleMethodCall:onPointerMoveCallMove
3115 result:^(id _Nullable result){
3123 [textInputPlugin handleMethodCall:onPointerMoveCallBackUp
3124 result:^(id _Nullable result){
3129 [subView removeFromSuperview];
3134- (void)testInteractiveKeyboardFindFirstResponderRecursive {
3136 [UIApplication.sharedApplication.keyWindow addSubview:inputView];
3138 [inputView reloadInputViews];
3139 [inputView becomeFirstResponder];
3141 UIView* firstResponder = UIApplication.sharedApplication.keyWindow.flutterFirstResponder;
3142 XCTAssertEqualObjects(inputView, firstResponder);
3146- (void)testInteractiveKeyboardFindFirstResponderRecursiveInMultipleSubviews {
3153 [subInputView addSubview:subFirstResponderInputView];
3154 [inputView addSubview:subInputView];
3155 [inputView addSubview:otherSubInputView];
3156 [UIApplication.sharedApplication.keyWindow addSubview:inputView];
3158 [inputView reloadInputViews];
3160 [subInputView reloadInputViews];
3162 [otherSubInputView reloadInputViews];
3164 [subFirstResponderInputView reloadInputViews];
3165 [subFirstResponderInputView becomeFirstResponder];
3167 UIView* firstResponder = UIApplication.sharedApplication.keyWindow.flutterFirstResponder;
3168 XCTAssertEqualObjects(subFirstResponderInputView, firstResponder);
3172- (void)testInteractiveKeyboardFindFirstResponderIsNilRecursive {
3174 [UIApplication.sharedApplication.keyWindow addSubview:inputView];
3176 [inputView reloadInputViews];
3178 UIView* firstResponder = UIApplication.sharedApplication.keyWindow.flutterFirstResponder;
3179 XCTAssertNil(firstResponder);
3183- (void)testInteractiveKeyboardDidResignFirstResponderDelegateisCalledAfterDismissedKeyboard {
3184 NSSet<UIScene*>* scenes = UIApplication.sharedApplication.connectedScenes;
3185 XCTAssertEqual(scenes.count, 1UL,
@"There must only be 1 scene for test");
3186 UIScene* scene = scenes.anyObject;
3187 XCTAssert([scene isKindOfClass:[UIWindowScene
class]],
@"Must be a window scene for test");
3188 UIWindowScene* windowScene = (UIWindowScene*)scene;
3189 XCTAssert(windowScene.windows.count > 0,
@"There must be at least 1 window for test");
3190 UIWindow*
window = windowScene.windows[0];
3191 [window addSubview:viewController.view];
3193 [viewController loadView];
3195 XCTestExpectation*
expectation = [[XCTestExpectation alloc]
3196 initWithDescription:
3197 @"didResignFirstResponder is called after screenshot keyboard dismissed."];
3198 OCMStub([
engine flutterTextInputView:[OCMArg
any] didResignFirstResponderWithTextInputClient:0])
3199 .andDo(^(NSInvocation* invocation) {
3200 [expectation fulfill];
3202 CGRect keyboardFrame = CGRectMake(0, 500, 500, 500);
3203 [NSNotificationCenter.defaultCenter
3204 postNotificationName:UIKeyboardWillShowNotification
3206 userInfo:@{UIKeyboardFrameEndUserInfoKey : @(keyboardFrame)}];
3210 [textInputPlugin handleMethodCall:initialMoveCall
3211 result:^(id _Nullable result){
3216 [textInputPlugin handleMethodCall:subsequentMoveCall
3217 result:^(id _Nullable result){
3223 [textInputPlugin handleMethodCall:pointerUpCall
3224 result:^(id _Nullable result){
3227 [
self waitForExpectations:@[ expectation ] timeout:2.0];
3231- (void)testInteractiveKeyboardScreenshotDismissedAfterPointerLiftedAboveMiddleYOfKeyboard {
3232 NSSet<UIScene*>* scenes = UIApplication.sharedApplication.connectedScenes;
3233 XCTAssertEqual(scenes.count, 1UL,
@"There must only be 1 scene for test");
3234 UIScene* scene = scenes.anyObject;
3235 XCTAssert([scene isKindOfClass:[UIWindowScene
class]],
@"Must be a window scene for test");
3236 UIWindowScene* windowScene = (UIWindowScene*)scene;
3237 XCTAssert(windowScene.windows.count > 0,
@"There must be at least 1 window for test");
3238 UIWindow*
window = windowScene.windows[0];
3239 [window addSubview:viewController.view];
3241 [viewController loadView];
3243 CGRect keyboardFrame = CGRectMake(0, 500, 500, 500);
3244 [NSNotificationCenter.defaultCenter
3245 postNotificationName:UIKeyboardWillShowNotification
3247 userInfo:@{UIKeyboardFrameEndUserInfoKey : @(keyboardFrame)}];
3251 [textInputPlugin handleMethodCall:initialMoveCall
3252 result:^(id _Nullable result){
3257 [textInputPlugin handleMethodCall:subsequentMoveCall
3258 result:^(id _Nullable result){
3264 [textInputPlugin handleMethodCall:subsequentMoveBackUpCall
3265 result:^(id _Nullable result){
3271 [textInputPlugin handleMethodCall:pointerUpCall
3272 result:^(id _Nullable result){
3274 NSPredicate* predicate = [NSPredicate predicateWithBlock:^BOOL(id item, NSDictionary* bindings) {
3275 return textInputPlugin.keyboardViewContainer.subviews.count == 0;
3278 [[XCTNSPredicateExpectation alloc] initWithPredicate:predicate object:nil];
3279 [
self waitForExpectations:@[ expectation ] timeout:10.0];
3283- (void)testInteractiveKeyboardKeyboardReappearsAfterPointerLiftedAboveMiddleYOfKeyboard {
3284 NSSet<UIScene*>* scenes = UIApplication.sharedApplication.connectedScenes;
3285 XCTAssertEqual(scenes.count, 1UL,
@"There must only be 1 scene for test");
3286 UIScene* scene = scenes.anyObject;
3287 XCTAssert([scene isKindOfClass:[UIWindowScene
class]],
@"Must be a window scene for test");
3288 UIWindowScene* windowScene = (UIWindowScene*)scene;
3289 XCTAssert(windowScene.windows.count > 0,
@"There must be at least 1 window for test");
3290 UIWindow*
window = windowScene.windows[0];
3291 [window addSubview:viewController.view];
3293 [viewController loadView];
3296 [UIApplication.sharedApplication.keyWindow addSubview:inputView];
3299 [inputView reloadInputViews];
3300 [inputView becomeFirstResponder];
3302 CGRect keyboardFrame = CGRectMake(0, 500, 500, 500);
3303 [NSNotificationCenter.defaultCenter
3304 postNotificationName:UIKeyboardWillShowNotification
3306 userInfo:@{UIKeyboardFrameEndUserInfoKey : @(keyboardFrame)}];
3310 [textInputPlugin handleMethodCall:initialMoveCall
3311 result:^(id _Nullable result){
3316 [textInputPlugin handleMethodCall:subsequentMoveCall
3317 result:^(id _Nullable result){
3323 [textInputPlugin handleMethodCall:subsequentMoveBackUpCall
3324 result:^(id _Nullable result){
3330 [textInputPlugin handleMethodCall:pointerUpCall
3331 result:^(id _Nullable result){
3333 NSPredicate* predicate = [NSPredicate predicateWithBlock:^BOOL(id item, NSDictionary* bindings) {
3334 return textInputPlugin.cachedFirstResponder.isFirstResponder;
3337 [[XCTNSPredicateExpectation alloc] initWithPredicate:predicate object:nil];
3338 [
self waitForExpectations:@[ expectation ] timeout:10.0];
3342- (void)testInteractiveKeyboardKeyboardAnimatesToOriginalPositionalOnPointerUp {
3343 NSSet<UIScene*>* scenes = UIApplication.sharedApplication.connectedScenes;
3344 XCTAssertEqual(scenes.count, 1UL,
@"There must only be 1 scene for test");
3345 UIScene* scene = scenes.anyObject;
3346 XCTAssert([scene isKindOfClass:[UIWindowScene
class]],
@"Must be a window scene for test");
3347 UIWindowScene* windowScene = (UIWindowScene*)scene;
3348 XCTAssert(windowScene.windows.count > 0,
@"There must be at least 1 window for test");
3349 UIWindow*
window = windowScene.windows[0];
3350 [window addSubview:viewController.view];
3352 [viewController loadView];
3355 [[XCTestExpectation alloc] initWithDescription:@"Keyboard animates to proper position."];
3356 CGRect keyboardFrame = CGRectMake(0, 500, 500, 500);
3357 [NSNotificationCenter.defaultCenter
3358 postNotificationName:UIKeyboardWillShowNotification
3360 userInfo:@{UIKeyboardFrameEndUserInfoKey : @(keyboardFrame)}];
3364 [textInputPlugin handleMethodCall:initialMoveCall
3365 result:^(id _Nullable result){
3370 [textInputPlugin handleMethodCall:subsequentMoveCall
3371 result:^(id _Nullable result){
3376 [textInputPlugin handleMethodCall:upwardVelocityMoveCall
3377 result:^(id _Nullable result){
3384 handleMethodCall:pointerUpCall
3385 result:^(id _Nullable result) {
3386 XCTAssertEqual(textInputPlugin.keyboardViewContainer.frame.origin.y,
3387 viewController.flutterScreenIfViewLoaded.bounds.size.height -
3388 keyboardFrame.origin.y);
3389 [expectation fulfill];
3394- (void)testInteractiveKeyboardKeyboardAnimatesToDismissalPositionalOnPointerUp {
3395 NSSet<UIScene*>* scenes = UIApplication.sharedApplication.connectedScenes;
3396 XCTAssertEqual(scenes.count, 1UL,
@"There must only be 1 scene for test");
3397 UIScene* scene = scenes.anyObject;
3398 XCTAssert([scene isKindOfClass:[UIWindowScene
class]],
@"Must be a window scene for test");
3399 UIWindowScene* windowScene = (UIWindowScene*)scene;
3400 XCTAssert(windowScene.windows.count > 0,
@"There must be at least 1 window for test");
3401 UIWindow*
window = windowScene.windows[0];
3402 [window addSubview:viewController.view];
3404 [viewController loadView];
3407 [[XCTestExpectation alloc] initWithDescription:@"Keyboard animates to proper position."];
3408 CGRect keyboardFrame = CGRectMake(0, 500, 500, 500);
3409 [NSNotificationCenter.defaultCenter
3410 postNotificationName:UIKeyboardWillShowNotification
3412 userInfo:@{UIKeyboardFrameEndUserInfoKey : @(keyboardFrame)}];
3416 [textInputPlugin handleMethodCall:initialMoveCall
3417 result:^(id _Nullable result){
3422 [textInputPlugin handleMethodCall:subsequentMoveCall
3423 result:^(id _Nullable result){
3430 handleMethodCall:pointerUpCall
3431 result:^(id _Nullable result) {
3432 XCTAssertEqual(textInputPlugin.keyboardViewContainer.frame.origin.y,
3433 viewController.flutterScreenIfViewLoaded.bounds.size.height);
3434 [expectation fulfill];
3438- (void)testInteractiveKeyboardShowKeyboardAndRemoveScreenshotAnimationIsNotImmediatelyEnable {
3439 [UIView setAnimationsEnabled:YES];
3440 [textInputPlugin showKeyboardAndRemoveScreenshot];
3442 UIView.areAnimationsEnabled,
3443 @"The animation should still be disabled following showKeyboardAndRemoveScreenshot");
3446- (void)testInteractiveKeyboardShowKeyboardAndRemoveScreenshotAnimationIsReenabledAfterDelay {
3447 [UIView setAnimationsEnabled:YES];
3448 [textInputPlugin showKeyboardAndRemoveScreenshot];
3450 NSPredicate* predicate = [NSPredicate predicateWithBlock:^BOOL(id item, NSDictionary* bindings) {
3452 return UIView.areAnimationsEnabled;
3455 [[XCTNSPredicateExpectation alloc] initWithPredicate:predicate object:nil];
3456 [
self waitForExpectations:@[ expectation ] timeout:10.0];
static SkISize times(const SkISize &size, float factor)
static void done(const char *config, const char *src, const char *srcOptions, const char *name)
static void testRect1(skiatest::Reporter *reporter, const char *filename)
static void copy(void *dst, const uint8_t *src, int width, int bpp, int deltaSrc, int offset, const SkPMColor ctable[])
void scribbleInteractionWillBeginWriting:(ios(14.0) API_AVAILABLE)
void updateEditingState()
void replaceRange:withText:(UITextRange *range, [withText] NSString *text)
void setSelectedTextRange:(UITextRange *selectedTextRange)
void scribbleInteractionDidFinishWriting:(ios(14.0) API_AVAILABLE)
void setMarkedRect:(CGRect markedRect)
NSString * textInRange:(UITextRange *range)
UIInputViewController * inputViewController()
BOOL isVisibleToAutofill()
void pressesEnded:withEvent:(NSSet< UIPress * > *presses, [withEvent] ios(9.0) API_AVAILABLE)
id< UITextInputTokenizer > tokenizer()
BOOL canBecomeFirstResponder()
void setMarkedText:selectedRange:(NSString *markedText, [selectedRange] NSRange markedSelectedRange)
NSArray * selectionRectsForRange:(UITextRange *range)
void beginFloatingCursorAtPoint:(CGPoint point)
BOOL shouldChangeTextInRange:replacementText:(UITextRange *range, [replacementText] NSString *text)
void setEditableTransform:(NSArray *matrix)
UITextRange * selectedTextRange()
void updateFloatingCursorAtPoint:(CGPoint point)
void selectAll:(id sender)
void setTextInputState:(NSDictionary *state)
void configureWithDictionary:(NSDictionary *configuration)
CGRect firstRectForRange:(UITextRange *range)
CGRect editMenuInteraction:targetRectForConfiguration:(UIEditMenuInteraction *interaction, [targetRectForConfiguration] ios(16.0) API_AVAILABLE)
void setTextInputClient:(int client)
id< FlutterTextInputDelegate > textInputDelegate()
void pressesBegan:withEvent:(NSSet< UIPress * > *presses, [withEvent] ios(9.0) API_AVAILABLE)
FlutterTextInputPlugin * textInputPlugin
UITextContentType textContentType()
void insertText:(NSString *text)
void resetScribbleInteractionStatusIfEnding()
void accessibilityElementDidBecomeFocused()
BOOL respondsToSelector:(SEL selector)
BOOL runWithEntrypoint:(nullable NSString *entrypoint)
void setBinaryMessenger:(FlutterBinaryMessengerRelay *binaryMessenger)
void flutterTextInputView:performAction:withClient:(FlutterTextInputView *textInputView, [performAction] FlutterTextInputAction action, [withClient] int client)
BOOL runWithEntrypoint:initialRoute:(nullable NSString *entrypoint,[initialRoute] nullable NSString *initialRoute)
FlutterTextInputPlugin * textInputPlugin()
FlutterViewController * viewController
instancetype sharedInstance()
instancetype methodCallWithMethodName:arguments:(NSString *method,[arguments] id _Nullable arguments)
UIView< UITextInput > * textInputView()
void startLiveTextInput()
NSArray< UIView * > * textInputViews()
UIIndirectScribbleInteractionDelegate UIViewController * viewController
BOOL showEditMenu:(ios(16.0) API_AVAILABLE)
UIView * keyboardViewContainer
NSMutableDictionary< NSString *, FlutterTextInputView * > * autofillContext
void handleMethodCall:result:(FlutterMethodCall *call,[result] FlutterResult result)
void showKeyboardAndRemoveScreenshot()
FlutterTextInputViewAccessibilityHider * inputHider
FlutterTextInputView * activeView
UIView * cachedFirstResponder
UIAccessibilityNotifications receivedNotification
id receivedNotificationTarget
BOOL isAccessibilityFocused
instancetype positionWithIndex:(NSUInteger index)
UITextStorageDirection affinity
instancetype selectionRectWithRect:position:(CGRect rect,[position] NSUInteger position)
instancetype selectionRectWithRect:position:writingDirection:(CGRect rect,[position] NSUInteger position,[writingDirection] NSWritingDirection writingDirection)
NSDictionary * _passwordTemplate
FlutterViewController * viewController
FlutterTextInputPlugin * textInputPlugin
NSArray< FlutterTextSelectionRect * > * selectionRects
UITextRange * markedTextRange
API_AVAILABLE(ios(13.0)) @interface FlutterTextPlaceholder UITextRange * selectedTextRange
CGRect caretRectForPosition
const CGRect kInvalidFirstRect
sk_sp< SkBlender > blender SkRect rect
font
Font Metadata and Metrics.
SIT bool any(const Vec< 1, T > &x)