Flutter Engine
The Flutter Engine
Instance Methods | List of all members
FlutterTextInputPluginTest Class Reference
Inheritance diagram for FlutterTextInputPluginTest:

Instance Methods

(void) - setUp [implementation]
 
(void) - tearDown [implementation]
 
(void) - setClientId:configuration: [implementation]
 
(void) - setTextInputShow [implementation]
 
(void) - setTextInputHide [implementation]
 
(void) - flushScheduledAsyncBlocks [implementation]
 
(NSMutableDictionary *) - mutableTemplateCopy [implementation]
 
(NSArray< FlutterTextInputView * > *) - installedInputViews [implementation]
 
(FlutterTextRange *) - getLineRangeFromTokenizer:atIndex: [implementation]
 
(void) - updateConfig: [implementation]
 
(void) - testWillNotCrashWhenViewControllerIsNil [implementation]
 
(void) - testInvokeStartLiveTextInput [implementation]
 
(void) - testNoDanglingEnginePointer [implementation]
 
(void) - testSecureInput [implementation]
 
(void) - testKeyboardType [implementation]
 
(void) - testVisiblePasswordUseAlphanumeric [implementation]
 
(void) - testSettingKeyboardTypeNoneDisablesSystemKeyboard [implementation]
 
(void) - testAutocorrectionPromptRectAppearsBeforeIOS17AndDoesNotAppearAfterIOS17 [implementation]
 
(void) - testIgnoresSelectionChangeIfSelectionIsDisabled [implementation]
 
(void) - testAutocorrectionPromptRectDoesNotAppearDuringScribble [implementation]
 
(void) - testInputHiderOverlapWithTextWhenScribbleIsDisabledAfterIOS17AndDoesNotOverlapBeforeIOS17 [implementation]
 
(void) - testTextRangeFromPositionMatchesUITextViewBehavior [implementation]
 
(void) - testTextInRange [implementation]
 
(void) - testStandardEditActions [implementation]
 
(void) - testDeletingBackward [implementation]
 
(void) - testSystemOnlyAddingPartialComposedCharacter [implementation]
 
(void) - testCachedComposedCharacterClearedAtKeyboardInteraction [implementation]
 
(void) - testPastingNonTextDisallowed [implementation]
 
(void) - testNoZombies [implementation]
 
(void) - testInputViewCrash [implementation]
 
(void) - testDoNotReuseInputViews [implementation]
 
(void) - ensureOnlyActiveViewCanBecomeFirstResponder [implementation]
 
(void) - testPropagatePressEventsToViewController [implementation]
 
(void) - testPropagatePressEventsToViewController2 [implementation]
 
(void) - testUpdateSecureTextEntry [implementation]
 
(void) - testInputActionContinueAction [implementation]
 
(void) - testDisablingAutocorrectDisablesSpellChecking [implementation]
 
(void) - testReplaceTestLocalAdjustSelectionAndMarkedTextRange [implementation]
 
(void) - testFlutterTextInputViewOnlyRespondsToInsertionPointColorBelowIOS17 [implementation]
 
(void) - testTextEditingDeltasAreGeneratedOnTextInput [implementation]
 
(void) - testTextEditingDeltasAreBatchedAndForwardedToFramework [implementation]
 
(void) - testTextEditingDeltasAreGeneratedOnSetMarkedTextReplacement [implementation]
 
(void) - testTextEditingDeltasAreGeneratedOnSetMarkedTextInsertion [implementation]
 
(void) - testTextEditingDeltasAreGeneratedOnSetMarkedTextDeletion [implementation]
 
(void) - testUITextInputCallsUpdateEditingStateOnce [implementation]
 
(void) - testUITextInputCallsUpdateEditingStateWithDeltaOnce [implementation]
 
(void) - testTextChangesDoNotTriggerUpdateEditingClient [implementation]
 
(void) - testTextChangesDoNotTriggerUpdateEditingClientWithDelta [implementation]
 
(void) - testUITextInputAvoidUnnecessaryUndateEditingClientCalls [implementation]
 
(void) - testCanCopyPasteWithScribbleEnabled [implementation]
 
(void) - testSetMarkedTextDuringScribbleDoesNotTriggerUpdateEditingClient [implementation]
 
(void) - testUpdateEditingClientNegativeSelection [implementation]
 
(void) - testUpdateEditingClientSelectionClamping [implementation]
 
(void) - testInputViewsHasNonNilInputDelegate [implementation]
 
(void) - testInputViewsDoNotHaveUITextInteractions [implementation]
 
(void) - testUpdateFirstRectForRange [implementation]
 
(void) - testFirstRectForRangeReturnsNoneZeroRectWhenScribbleIsEnabled [implementation]
 
(void) - testFirstRectForRangeReturnsCorrectRectOnASingleLineLeftToRight [implementation]
 
(void) - testFirstRectForRangeReturnsCorrectRectOnASingleLineRightToLeft [implementation]
 
(void) - testFirstRectForRangeReturnsCorrectRectOnMultipleLinesLeftToRight [implementation]
 
(void) - testFirstRectForRangeReturnsCorrectRectOnMultipleLinesRightToLeft [implementation]
 
(void) - testFirstRectForRangeReturnsCorrectRectOnSingleLineWithVaryingMinYAndMaxYLeftToRight [implementation]
 
(void) - testFirstRectForRangeReturnsCorrectRectOnSingleLineWithVaryingMinYAndMaxYRightToLeft [implementation]
 
(void) - testFirstRectForRangeReturnsCorrectRectWithOverlappingRectsExceedingThresholdLeftToRight [implementation]
 
(void) - testFirstRectForRangeReturnsCorrectRectWithOverlappingRectsExceedingThresholdRightToLeft [implementation]
 
(void) - testFirstRectForRangeReturnsCorrectRectWithOverlappingRectsWithinThresholdLeftToRight [implementation]
 
(void) - testFirstRectForRangeReturnsCorrectRectWithOverlappingRectsWithinThresholdRightToLeft [implementation]
 
(void) - testClosestPositionToPoint [implementation]
 
(void) - testClosestPositionToPointRTL [implementation]
 
(void) - testSelectionRectsForRange [implementation]
 
(void) - testClosestPositionToPointWithinRange [implementation]
 
(void) - testClosestPositionToPointWithPartialSelectionRects [implementation]
 
(void) - testFloatingCursorDoesNotThrow [implementation]
 
(void) - testFloatingCursor [implementation]
 
(void) - testInsertTextAddsPlaceholderSelectionRects [implementation]
 
(NSMutableDictionary *) - mutablePasswordTemplateCopy [implementation]
 
(NSArray< FlutterTextInputView * > *) - viewsVisibleToAutofill [implementation]
 
(void) - commitAutofillContextAndVerify [implementation]
 
(void) - testDisablingAutofillOnInputClient [implementation]
 
(void) - testAutofillEnabledByDefault [implementation]
 
(void) - testAutofillContext [implementation]
 
(void) - testCommitAutofillContext [implementation]
 
(void) - testAutofillInputViews [implementation]
 
(void) - testPasswordAutofillHack [implementation]
 
(void) - testClearAutofillContextClearsSelection [implementation]
 
(void) - testGarbageInputViewsAreNotRemovedImmediately [implementation]
 
(void) - testScribbleSetSelectionRects [implementation]
 
(void) - testDecommissionedViewAreNotReusedByAutofill [implementation]
 
(void) - testInitialActiveViewCantAccessTextInputDelegate [implementation]
 
(void) - testUITextInputAccessibilityNotHiddenWhenShowed [implementation]
 
(void) - testFlutterTextInputViewDirectFocusToBackingTextInput [implementation]
 
(void) - testFlutterTokenizerCanParseLines [implementation]
 
(void) - testFlutterTokenizerLineEnclosingEndOfDocumentInBackwardDirectionShouldNotReturnNil [implementation]
 
(void) - testFlutterTokenizerLineEnclosingEndOfDocumentInForwardDirectionShouldReturnNilOnIOS17 [implementation]
 
(void) - testFlutterTokenizerLineEnclosingOutOfRangePositionShouldReturnNilOnIOS17 [implementation]
 
(void) - testFlutterTextInputPluginRetainsFlutterTextInputView [implementation]
 
(void) - testFlutterTextInputPluginHostViewNilCrash [implementation]
 
(void) - testFlutterTextInputPluginHostViewNotNil [implementation]
 
(void) - testSetPlatformViewClient [implementation]
 
(void) - testEditMenu_shouldSetupEditMenuDelegateCorrectly [implementation]
 
(void) - testEditMenu_shouldNotPresentEditMenuIfNotFirstResponder [implementation]
 
(void) - testEditMenu_shouldPresentEditMenuWithCorrectConfiguration [implementation]
 
(void) - testEditMenu_shouldPresentEditMenuWithCorectTargetRect [implementation]
 
(void) - testInteractiveKeyboardAfterUserScrollWillResignFirstResponder [implementation]
 
(void) - testInteractiveKeyboardAfterUserScrollToTopOfKeyboardWillTakeScreenshot [implementation]
 
(void) - testInteractiveKeyboardScreenshotWillBeMovedDownAfterUserScroll [implementation]
 
(void) - testInteractiveKeyboardScreenshotWillBeMovedToOrginalPositionAfterUserScroll [implementation]
 
(void) - testInteractiveKeyboardFindFirstResponderRecursive [implementation]
 
(void) - testInteractiveKeyboardFindFirstResponderRecursiveInMultipleSubviews [implementation]
 
(void) - testInteractiveKeyboardFindFirstResponderIsNilRecursive [implementation]
 
(void) - testInteractiveKeyboardDidResignFirstResponderDelegateisCalledAfterDismissedKeyboard [implementation]
 
(void) - testInteractiveKeyboardScreenshotDismissedAfterPointerLiftedAboveMiddleYOfKeyboard [implementation]
 
(void) - testInteractiveKeyboardKeyboardReappearsAfterPointerLiftedAboveMiddleYOfKeyboard [implementation]
 
(void) - testInteractiveKeyboardKeyboardAnimatesToOriginalPositionalOnPointerUp [implementation]
 
(void) - testInteractiveKeyboardKeyboardAnimatesToDismissalPositionalOnPointerUp [implementation]
 
(void) - testInteractiveKeyboardShowKeyboardAndRemoveScreenshotAnimationIsNotImmediatelyEnable [implementation]
 
(void) - testInteractiveKeyboardShowKeyboardAndRemoveScreenshotAnimationIsReenabledAfterDelay [implementation]
 

Detailed Description

Definition at line 83 of file FlutterTextInputPluginTest.mm.

Method Documentation

◆ commitAutofillContextAndVerify

- (void) commitAutofillContextAndVerify
implementation

Definition at line 92 of file FlutterTextInputPluginTest.mm.

2279 {
2280 FlutterMethodCall* methodCall =
2281 [FlutterMethodCall methodCallWithMethodName:@"TextInput.finishAutofillContext"
2282 arguments:@YES];
2283 [textInputPlugin handleMethodCall:methodCall
2284 result:^(id _Nullable result){
2285 }];
2286
2287 XCTAssertEqual(self.viewsVisibleToAutofill.count,
2288 [textInputPlugin.activeView isVisibleToAutofill] ? 1ul : 0ul);
2289 XCTAssertNotEqual(textInputPlugin.textInputView, nil);
2290 // The active view should still be installed so it doesn't get
2291 // deallocated.
2292 XCTAssertEqual(self.installedInputViews.count, 1ul);
2293 XCTAssertEqual(textInputPlugin.autofillContext.count, 0ul);
2294}
instancetype methodCallWithMethodName:arguments:(NSString *method,[arguments] id _Nullable arguments)
NSArray< FlutterTextInputView * > * viewsVisibleToAutofill()
NSArray< FlutterTextInputView * > * installedInputViews()
UIView< UITextInput > * textInputView()
NSMutableDictionary< NSString *, FlutterTextInputView * > * autofillContext
FlutterTextInputView * activeView
FlutterTextInputPlugin * textInputPlugin

◆ ensureOnlyActiveViewCanBecomeFirstResponder

- (void) ensureOnlyActiveViewCanBecomeFirstResponder
implementation

Definition at line 92 of file FlutterTextInputPluginTest.mm.

672 {
673 for (FlutterTextInputView* inputView in self.installedInputViews) {
674 XCTAssertEqual(inputView.canBecomeFirstResponder, inputView == textInputPlugin.activeView);
675 }
676}

◆ flushScheduledAsyncBlocks

- (void) flushScheduledAsyncBlocks
implementation

Definition at line 92 of file FlutterTextInputPluginTest.mm.

144 {
145 __block bool done = false;
146 XCTestExpectation* expectation =
147 [[XCTestExpectation alloc] initWithDescription:@"Testing on main queue"];
148 dispatch_async(dispatch_get_main_queue(), ^{
149 done = true;
150 });
151 dispatch_async(dispatch_get_main_queue(), ^{
152 XCTAssertTrue(done);
153 [expectation fulfill];
154 });
155 [self waitForExpectations:@[ expectation ] timeout:10];
156}
static void done(const char *config, const char *src, const char *srcOptions, const char *name)
Definition: DM.cpp:263

◆ getLineRangeFromTokenizer:atIndex:

- (FlutterTextRange *) getLineRangeFromTokenizer: (id<UITextInputTokenizer>)  tokenizer
atIndex: (NSInteger)  index 
implementation

Definition at line 92 of file FlutterTextInputPluginTest.mm.

181 :(id<UITextInputTokenizer>)tokenizer
182 atIndex:(NSInteger)index {
183 UITextRange* range =
184 [tokenizer rangeEnclosingPosition:[FlutterTextPosition positionWithIndex:index]
185 withGranularity:UITextGranularityLine
186 inDirection:UITextLayoutDirectionRight];
187 XCTAssertTrue([range isKindOfClass:[FlutterTextRange class]]);
188 return (FlutterTextRange*)range;
189}
instancetype positionWithIndex:(NSUInteger index)

◆ installedInputViews

- (NSArray< FlutterTextInputView * > *) installedInputViews
implementation

Definition at line 92 of file FlutterTextInputPluginTest.mm.

175 {
176 return (NSArray<FlutterTextInputView*>*)[textInputPlugin.textInputViews
177 filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"self isKindOfClass: %@",
178 [FlutterTextInputView class]]];
179}

◆ mutablePasswordTemplateCopy

- (NSMutableDictionary *) mutablePasswordTemplateCopy
implementation

Definition at line 92 of file FlutterTextInputPluginTest.mm.

2258 {
2259 if (!_passwordTemplate) {
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
2268 };
2269 }
2270
2271 return [_passwordTemplate mutableCopy];
2272}
NSDictionary * _passwordTemplate

◆ mutableTemplateCopy

- (NSMutableDictionary *) mutableTemplateCopy
implementation

Definition at line 92 of file FlutterTextInputPluginTest.mm.

158 {
159 if (!_template) {
160 _template = @{
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,
169 };
170 }
171
172 return [_template mutableCopy];
173}

◆ setClientId:configuration:

- (void) setClientId: (int clientId
configuration: (NSDictionary*)  config 
implementation

Definition at line 92 of file FlutterTextInputPluginTest.mm.

119 :(int)clientId configuration:(NSDictionary*)config {
120 FlutterMethodCall* setClientCall =
121 [FlutterMethodCall methodCallWithMethodName:@"TextInput.setClient"
122 arguments:@[ [NSNumber numberWithInt:clientId], config ]];
123 [textInputPlugin handleMethodCall:setClientCall
124 result:^(id _Nullable result){
125 }];
126}

◆ setTextInputHide

- (void) setTextInputHide
implementation

Definition at line 92 of file FlutterTextInputPluginTest.mm.

136 {
137 FlutterMethodCall* setClientCall = [FlutterMethodCall methodCallWithMethodName:@"TextInput.hide"
138 arguments:@[]];
139 [textInputPlugin handleMethodCall:setClientCall
140 result:^(id _Nullable result){
141 }];
142}

◆ setTextInputShow

- (void) setTextInputShow
implementation

Definition at line 92 of file FlutterTextInputPluginTest.mm.

128 {
129 FlutterMethodCall* setClientCall = [FlutterMethodCall methodCallWithMethodName:@"TextInput.show"
130 arguments:@[]];
131 [textInputPlugin handleMethodCall:setClientCall
132 result:^(id _Nullable result){
133 }];
134}

◆ setUp

- (void) setUp
implementation

Definition at line 92 of file FlutterTextInputPluginTest.mm.

95 {
96 [super setUp];
97 engine = OCMClassMock([FlutterEngine class]);
98
99 textInputPlugin = [[FlutterTextInputPlugin alloc] initWithDelegate:engine];
100
101 viewController = [[FlutterViewController alloc] init];
103
104 // Clear pasteboard between tests.
105 UIPasteboard.generalPasteboard.items = @[];
106}
UIIndirectScribbleInteractionDelegate UIViewController * viewController
FlutterViewController * viewController

◆ tearDown

- (void) tearDown
implementation

Definition at line 92 of file FlutterTextInputPluginTest.mm.

108 {
109 textInputPlugin = nil;
110 engine = nil;
111 [textInputPlugin.autofillContext removeAllObjects];
112 [textInputPlugin cleanUpViewHierarchy:YES clearText:YES delayRemoval:NO];
113 [[[[textInputPlugin textInputView] superview] subviews]
114 makeObjectsPerformSelector:@selector(removeFromSuperview)];
115 viewController = nil;
116 [super tearDown];
117}

◆ testAutocorrectionPromptRectAppearsBeforeIOS17AndDoesNotAppearAfterIOS17

- (void) testAutocorrectionPromptRectAppearsBeforeIOS17AndDoesNotAppearAfterIOS17
implementation

Definition at line 92 of file FlutterTextInputPluginTest.mm.

338 {
339 FlutterTextInputView* inputView = [[FlutterTextInputView alloc] initWithOwner:textInputPlugin];
340 [inputView firstRectForRange:[FlutterTextRange rangeWithNSRange:NSMakeRange(0, 1)]];
341
342 if (@available(iOS 17.0, *)) {
343 // Auto-correction prompt is disabled in iOS 17+.
344 OCMVerify(never(), [engine flutterTextInputView:inputView
345 showAutocorrectionPromptRectForStart:0
346 end:1
347 withClient:0]);
348 } else {
349 OCMVerify([engine flutterTextInputView:inputView
350 showAutocorrectionPromptRectForStart:0
351 end:1
352 withClient:0]);
353 }
354}
CGRect firstRectForRange:(UITextRange *range)
glong glong end
instancetype rangeWithNSRange:(NSRange range)

◆ testAutocorrectionPromptRectDoesNotAppearDuringScribble

- (void) testAutocorrectionPromptRectDoesNotAppearDuringScribble
implementation

Definition at line 92 of file FlutterTextInputPluginTest.mm.

384 {
385 // Auto-correction prompt is disabled in iOS 17+.
386 if (@available(iOS 17.0, *)) {
387 return;
388 }
389
390 if (@available(iOS 14.0, *)) {
391 FlutterTextInputView* inputView = [[FlutterTextInputView alloc] initWithOwner:textInputPlugin];
392
393 __block int callCount = 0;
394 OCMStub([engine flutterTextInputView:inputView
395 showAutocorrectionPromptRectForStart:0
396 end:1
397 withClient:0])
398 .andDo(^(NSInvocation* invocation) {
399 callCount++;
400 });
401
402 [inputView firstRectForRange:[FlutterTextRange rangeWithNSRange:NSMakeRange(0, 1)]];
403 // showAutocorrectionPromptRectForStart fires in response to firstRectForRange
404 XCTAssertEqual(callCount, 1);
405
406 UIScribbleInteraction* scribbleInteraction =
407 [[UIScribbleInteraction alloc] initWithDelegate:inputView];
408
409 [inputView scribbleInteractionWillBeginWriting:scribbleInteraction];
410 [inputView firstRectForRange:[FlutterTextRange rangeWithNSRange:NSMakeRange(0, 1)]];
411 // showAutocorrectionPromptRectForStart does not fire in response to setMarkedText during a
412 // scribble interaction.firstRectForRange
413 XCTAssertEqual(callCount, 1);
414
415 [inputView scribbleInteractionDidFinishWriting:scribbleInteraction];
417 [inputView firstRectForRange:[FlutterTextRange rangeWithNSRange:NSMakeRange(0, 1)]];
418 // showAutocorrectionPromptRectForStart fires in response to firstRectForRange.
419 XCTAssertEqual(callCount, 2);
420
421 inputView.scribbleFocusStatus = FlutterScribbleFocusStatusFocusing;
422 [inputView firstRectForRange:[FlutterTextRange rangeWithNSRange:NSMakeRange(0, 1)]];
423 // showAutocorrectionPromptRectForStart does not fire in response to firstRectForRange during a
424 // scribble-initiated focus.
425 XCTAssertEqual(callCount, 2);
426
427 inputView.scribbleFocusStatus = FlutterScribbleFocusStatusFocused;
428 [inputView firstRectForRange:[FlutterTextRange rangeWithNSRange:NSMakeRange(0, 1)]];
429 // showAutocorrectionPromptRectForStart does not fire in response to firstRectForRange after a
430 // scribble-initiated focus.
431 XCTAssertEqual(callCount, 2);
432
433 inputView.scribbleFocusStatus = FlutterScribbleFocusStatusUnfocused;
434 [inputView firstRectForRange:[FlutterTextRange rangeWithNSRange:NSMakeRange(0, 1)]];
435 // showAutocorrectionPromptRectForStart fires in response to firstRectForRange.
436 XCTAssertEqual(callCount, 3);
437 }
438}
void scribbleInteractionWillBeginWriting:(ios(14.0) API_AVAILABLE)
void scribbleInteractionDidFinishWriting:(ios(14.0) API_AVAILABLE)

◆ testAutofillContext

- (void) testAutofillContext
implementation

Definition at line 92 of file FlutterTextInputPluginTest.mm.

2320 {
2321 NSMutableDictionary* field1 = self.mutableTemplateCopy;
2322
2323 [field1 setValue:@{
2324 @"uniqueIdentifier" : @"field1",
2325 @"hints" : @[ @"hint1" ],
2326 @"editingValue" : @{@"text" : @""}
2327 }
2328 forKey:@"autofill"];
2329
2330 NSMutableDictionary* field2 = self.mutablePasswordTemplateCopy;
2331 [field2 setValue:@{
2332 @"uniqueIdentifier" : @"field2",
2333 @"hints" : @[ @"hint2" ],
2334 @"editingValue" : @{@"text" : @""}
2335 }
2336 forKey:@"autofill"];
2337
2338 NSMutableDictionary* config = [field1 mutableCopy];
2339 [config setValue:@[ field1, field2 ] forKey:@"fields"];
2340
2341 [self setClientId:123 configuration:config];
2342 XCTAssertEqual(self.viewsVisibleToAutofill.count, 2ul);
2343
2344 XCTAssertEqual(textInputPlugin.autofillContext.count, 2ul);
2345
2346 [textInputPlugin cleanUpViewHierarchy:NO clearText:YES delayRemoval:NO];
2347 XCTAssertEqual(self.installedInputViews.count, 2ul);
2349 [self ensureOnlyActiveViewCanBecomeFirstResponder];
2350
2351 // The configuration changes.
2352 NSMutableDictionary* field3 = self.mutablePasswordTemplateCopy;
2353 [field3 setValue:@{
2354 @"uniqueIdentifier" : @"field3",
2355 @"hints" : @[ @"hint3" ],
2356 @"editingValue" : @{@"text" : @""}
2357 }
2358 forKey:@"autofill"];
2359
2360 NSMutableDictionary* oldContext = textInputPlugin.autofillContext;
2361 // Replace field2 with field3.
2362 [config setValue:@[ field1, field3 ] forKey:@"fields"];
2363
2364 [self setClientId:123 configuration:config];
2365
2366 XCTAssertEqual(self.viewsVisibleToAutofill.count, 2ul);
2367 XCTAssertEqual(textInputPlugin.autofillContext.count, 3ul);
2368
2369 [textInputPlugin cleanUpViewHierarchy:NO clearText:YES delayRemoval:NO];
2370 XCTAssertEqual(self.installedInputViews.count, 3ul);
2372 [self ensureOnlyActiveViewCanBecomeFirstResponder];
2373
2374 // Old autofill input fields are still installed and reused.
2375 for (NSString* key in oldContext.allKeys) {
2376 XCTAssertEqual(oldContext[key], textInputPlugin.autofillContext[key]);
2377 }
2378
2379 // Switch to a password field that has no contentType and is not in an AutofillGroup.
2380 config = self.mutablePasswordTemplateCopy;
2381
2382 oldContext = textInputPlugin.autofillContext;
2383 [self setClientId:124 configuration:config];
2384 [self ensureOnlyActiveViewCanBecomeFirstResponder];
2385
2386 XCTAssertEqual(self.viewsVisibleToAutofill.count, 1ul);
2387 XCTAssertEqual(textInputPlugin.autofillContext.count, 3ul);
2388
2389 [textInputPlugin cleanUpViewHierarchy:NO clearText:YES delayRemoval:NO];
2390 XCTAssertEqual(self.installedInputViews.count, 4ul);
2391
2392 // Old autofill input fields are still installed and reused.
2393 for (NSString* key in oldContext.allKeys) {
2394 XCTAssertEqual(oldContext[key], textInputPlugin.autofillContext[key]);
2395 }
2396 // The active view should change.
2397 XCTAssertNotEqual(textInputPlugin.textInputView, textInputPlugin.autofillContext[@"field1"]);
2398 [self ensureOnlyActiveViewCanBecomeFirstResponder];
2399
2400 // Switch to a similar password field, the previous field should be reused.
2401 oldContext = textInputPlugin.autofillContext;
2402 [self setClientId:200 configuration:config];
2403
2404 // Reuse the input view instance from the last time.
2405 XCTAssertEqual(self.viewsVisibleToAutofill.count, 1ul);
2406 XCTAssertEqual(textInputPlugin.autofillContext.count, 3ul);
2407
2408 [textInputPlugin cleanUpViewHierarchy:NO clearText:YES delayRemoval:NO];
2409 XCTAssertEqual(self.installedInputViews.count, 4ul);
2410
2411 // Old autofill input fields are still installed and reused.
2412 for (NSString* key in oldContext.allKeys) {
2413 XCTAssertEqual(oldContext[key], textInputPlugin.autofillContext[key]);
2414 }
2415 XCTAssertNotEqual(textInputPlugin.textInputView, textInputPlugin.autofillContext[@"field1"]);
2416 [self ensureOnlyActiveViewCanBecomeFirstResponder];
2417}

◆ testAutofillEnabledByDefault

- (void) testAutofillEnabledByDefault
implementation

Definition at line 92 of file FlutterTextInputPluginTest.mm.

2308 {
2309 NSDictionary* config = self.mutableTemplateCopy;
2310 [config setValue:@"NO" forKey:@"obscureText"];
2311 [config setValue:@{@"uniqueIdentifier" : @"field1", @"editingValue" : @{@"text" : @""}}
2312 forKey:@"autofill"];
2313
2314 [self setClientId:123 configuration:config];
2315
2316 FlutterTextInputView* inputView = self.installedInputViews[0];
2317 XCTAssertNil(inputView.textContentType);
2318}
UITextContentType textContentType()

◆ testAutofillInputViews

- (void) testAutofillInputViews
implementation

Definition at line 92 of file FlutterTextInputPluginTest.mm.

2486 {
2487 NSMutableDictionary* field1 = self.mutableTemplateCopy;
2488 [field1 setValue:@{
2489 @"uniqueIdentifier" : @"field1",
2490 @"hints" : @[ @"hint1" ],
2491 @"editingValue" : @{@"text" : @""}
2492 }
2493 forKey:@"autofill"];
2494
2495 NSMutableDictionary* field2 = self.mutablePasswordTemplateCopy;
2496 [field2 setValue:@{
2497 @"uniqueIdentifier" : @"field2",
2498 @"hints" : @[ @"hint2" ],
2499 @"editingValue" : @{@"text" : @""}
2500 }
2501 forKey:@"autofill"];
2502
2503 NSMutableDictionary* config = [field1 mutableCopy];
2504 [config setValue:@[ field1, field2 ] forKey:@"fields"];
2505
2506 [self setClientId:123 configuration:config];
2507 [self ensureOnlyActiveViewCanBecomeFirstResponder];
2508
2509 // Find all the FlutterTextInputViews we created.
2510 NSArray<FlutterTextInputView*>* inputFields = self.installedInputViews;
2511
2512 // Both fields are installed and visible because it's a password group.
2513 XCTAssertEqual(inputFields.count, 2ul);
2514 XCTAssertEqual(self.viewsVisibleToAutofill.count, 2ul);
2515
2516 // Find the inactive autofillable input field.
2517 FlutterTextInputView* inactiveView = inputFields[1];
2518 [inactiveView replaceRange:[FlutterTextRange rangeWithNSRange:NSMakeRange(0, 0)]
2519 withText:@"Autofilled!"];
2520 [self ensureOnlyActiveViewCanBecomeFirstResponder];
2521
2522 // Verify behavior.
2523 OCMVerify([engine flutterTextInputView:inactiveView
2524 updateEditingClient:0
2525 withState:[OCMArg isNotNil]
2526 withTag:@"field2"]);
2527}
void replaceRange:withText:(UITextRange *range, [withText] NSString *text)

◆ testCachedComposedCharacterClearedAtKeyboardInteraction

- (void) testCachedComposedCharacterClearedAtKeyboardInteraction
implementation

Definition at line 92 of file FlutterTextInputPluginTest.mm.

605 {
606 NSDictionary* config = self.mutableTemplateCopy;
607 [self setClientId:123 configuration:config];
608 NSArray<FlutterTextInputView*>* inputFields = self.installedInputViews;
609 FlutterTextInputView* inputView = inputFields[0];
610
611 [inputView insertText:@"👨‍👩‍👧‍👦"];
612 [inputView deleteBackward];
613 [inputView shouldChangeTextInRange:OCMClassMock([UITextRange class]) replacementText:@""];
614
615 // Insert the first unichar in the emoji.
616 NSString* brokenEmoji = [@"👨‍👩‍👧‍👦" substringWithRange:NSMakeRange(0, 1)];
617 [inputView insertText:brokenEmoji];
618 [inputView insertText:@"아"];
619
620 NSString* finalText = [NSString stringWithFormat:@"%@아", brokenEmoji];
621 XCTAssertEqualObjects(inputView.text, finalText);
622}
BOOL shouldChangeTextInRange:replacementText:(UITextRange *range, [replacementText] NSString *text)
void insertText:(NSString *text)

◆ testCanCopyPasteWithScribbleEnabled

- (void) testCanCopyPasteWithScribbleEnabled
implementation

Definition at line 92 of file FlutterTextInputPluginTest.mm.

1289 {
1290 if (@available(iOS 14.0, *)) {
1291 NSDictionary* config = self.mutableTemplateCopy;
1292 [self setClientId:123 configuration:config];
1293 NSArray<FlutterTextInputView*>* inputFields = self.installedInputViews;
1294 FlutterTextInputView* inputView = inputFields[0];
1295
1296 FlutterTextInputView* mockInputView = OCMPartialMock(inputView);
1297 OCMStub([mockInputView isScribbleAvailable]).andReturn(YES);
1298
1299 [mockInputView insertText:@"aaaa"];
1300 [mockInputView selectAll:nil];
1301
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"]);
1306
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"]);
1312 }
1313}
BOOL isScribbleAvailable
Definition: copy.py:1

◆ testClearAutofillContextClearsSelection

- (void) testClearAutofillContextClearsSelection
implementation

Definition at line 92 of file FlutterTextInputPluginTest.mm.

2545 {
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
2553 };
2554 [regularField setValue:@{
2555 @"uniqueIdentifier" : @"field2",
2556 @"hints" : @[ @"hint2" ],
2557 @"editingValue" : editingValue,
2558 }
2559 forKey:@"autofill"];
2560 [regularField addEntriesFromDictionary:editingValue];
2561 [self setClientId:123 configuration:regularField];
2562 [self ensureOnlyActiveViewCanBecomeFirstResponder];
2563 XCTAssertEqual(self.installedInputViews.count, 1ul);
2564
2565 FlutterTextInputView* oldInputView = self.installedInputViews[0];
2566 XCTAssert([oldInputView.text isEqualToString:@"REGULAR_TEXT_FIELD"]);
2567 FlutterTextRange* selectionRange = (FlutterTextRange*)oldInputView.selectedTextRange;
2568 XCTAssert(NSEqualRanges(selectionRange.range, NSMakeRange(1, 3)));
2569
2570 // Replace the original password field with new one. This should remove
2571 // the old password field, but not immediately.
2572 [self setClientId:124 configuration:self.mutablePasswordTemplateCopy];
2573 [self ensureOnlyActiveViewCanBecomeFirstResponder];
2574
2575 XCTAssertEqual(self.installedInputViews.count, 2ul);
2576
2577 [textInputPlugin cleanUpViewHierarchy:NO clearText:YES delayRemoval:NO];
2578 XCTAssertEqual(self.installedInputViews.count, 1ul);
2579
2580 // Verify the old input view is properly cleaned up.
2581 XCTAssert([oldInputView.text isEqualToString:@""]);
2582 selectionRange = (FlutterTextRange*)oldInputView.selectedTextRange;
2583 XCTAssert(NSEqualRanges(selectionRange.range, NSMakeRange(0, 0)));
2584}

◆ testClosestPositionToPoint

- (void) testClosestPositionToPoint
implementation

Definition at line 92 of file FlutterTextInputPluginTest.mm.

1864 {
1865 FlutterTextInputView* inputView = [[FlutterTextInputView alloc] initWithOwner:textInputPlugin];
1866 [inputView setTextInputState:@{@"text" : @"COMPOSING"}];
1867
1868 // Minimize the vertical distance from the center of the rects first
1869 [inputView setSelectionRects:@[
1870 [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(0, 0, 100, 100) position:0U],
1871 [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(0, 100, 100, 100) position:1U],
1872 [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(0, 200, 100, 100) position:2U],
1873 ]];
1874 CGPoint point = CGPointMake(150, 150);
1875 XCTAssertEqual(2U, ((FlutterTextPosition*)[inputView closestPositionToPoint:point]).index);
1876 XCTAssertEqual(UITextStorageDirectionBackward,
1877 ((FlutterTextPosition*)[inputView closestPositionToPoint:point]).affinity);
1878
1879 // Then, if the point is above the bottom of the closest rects vertically, get the closest x
1880 // origin
1881 [inputView setSelectionRects:@[
1882 [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(0, 0, 100, 100) position:0U],
1883 [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(0, 100, 100, 100) position:1U],
1884 [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(100, 100, 100, 100) position:2U],
1885 [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(200, 100, 100, 100) position:3U],
1886 [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(0, 200, 100, 100) position:4U],
1887 ]];
1888 point = CGPointMake(125, 150);
1889 XCTAssertEqual(2U, ((FlutterTextPosition*)[inputView closestPositionToPoint:point]).index);
1890 XCTAssertEqual(UITextStorageDirectionForward,
1891 ((FlutterTextPosition*)[inputView closestPositionToPoint:point]).affinity);
1892
1893 // However, if the point is below the bottom of the closest rects vertically, get the position
1894 // farthest to the right
1895 [inputView setSelectionRects:@[
1896 [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(0, 0, 100, 100) position:0U],
1897 [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(0, 100, 100, 100) position:1U],
1898 [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(100, 100, 100, 100) position:2U],
1899 [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(200, 100, 100, 100) position:3U],
1900 [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(0, 300, 100, 100) position:4U],
1901 ]];
1902 point = CGPointMake(125, 201);
1903 XCTAssertEqual(4U, ((FlutterTextPosition*)[inputView closestPositionToPoint:point]).index);
1904 XCTAssertEqual(UITextStorageDirectionBackward,
1905 ((FlutterTextPosition*)[inputView closestPositionToPoint:point]).affinity);
1906
1907 // Also check a point at the right edge of the last selection rect
1908 [inputView setSelectionRects:@[
1909 [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(0, 0, 100, 100) position:0U],
1910 [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(0, 100, 100, 100) position:1U],
1911 [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(100, 100, 100, 100) position:2U],
1912 [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(200, 100, 100, 100) position:3U],
1913 ]];
1914 point = CGPointMake(125, 250);
1915 XCTAssertEqual(4U, ((FlutterTextPosition*)[inputView closestPositionToPoint:point]).index);
1916 XCTAssertEqual(UITextStorageDirectionBackward,
1917 ((FlutterTextPosition*)[inputView closestPositionToPoint:point]).affinity);
1918
1919 // Minimize vertical distance if the difference is more than 1 point.
1920 [inputView setSelectionRects:@[
1921 [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(0, 2, 100, 100) position:0U],
1922 [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(100, 2, 100, 100) position:1U],
1923 [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(200, 0, 100, 100) position:2U],
1924 ]];
1925 point = CGPointMake(110, 50);
1926 XCTAssertEqual(2U, ((FlutterTextPosition*)[inputView closestPositionToPoint:point]).index);
1927 XCTAssertEqual(UITextStorageDirectionForward,
1928 ((FlutterTextPosition*)[inputView closestPositionToPoint:point]).affinity);
1929
1930 // In floating cursor mode, the vertical difference is allowed to be 10 points.
1931 // The closest horizontal position will now win.
1932 [inputView beginFloatingCursorAtPoint:CGPointZero];
1933 XCTAssertEqual(1U, ((FlutterTextPosition*)[inputView closestPositionToPoint:point]).index);
1934 XCTAssertEqual(UITextStorageDirectionForward,
1935 ((FlutterTextPosition*)[inputView closestPositionToPoint:point]).affinity);
1936 [inputView endFloatingCursor];
1937}
void beginFloatingCursorAtPoint:(CGPoint point)
void setTextInputState:(NSDictionary *state)
instancetype selectionRectWithRect:position:(CGRect rect,[position] NSUInteger position)

◆ testClosestPositionToPointRTL

- (void) testClosestPositionToPointRTL
implementation

Definition at line 92 of file FlutterTextInputPluginTest.mm.

1939 {
1940 FlutterTextInputView* inputView = [[FlutterTextInputView alloc] initWithOwner:textInputPlugin];
1941 [inputView setTextInputState:@{@"text" : @"COMPOSING"}];
1942
1943 [inputView setSelectionRects:@[
1944 [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(200, 0, 100, 100)
1945 position:0U
1946 writingDirection:NSWritingDirectionRightToLeft],
1947 [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(100, 0, 100, 100)
1948 position:1U
1949 writingDirection:NSWritingDirectionRightToLeft],
1950 [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(0, 0, 100, 100)
1951 position:2U
1952 writingDirection:NSWritingDirectionRightToLeft],
1953 [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(0, 100, 100, 100)
1954 position:3U
1955 writingDirection:NSWritingDirectionRightToLeft],
1956 ]];
1957 FlutterTextPosition* position =
1958 (FlutterTextPosition*)[inputView closestPositionToPoint:CGPointMake(275, 50)];
1959 XCTAssertEqual(0U, position.index);
1960 XCTAssertEqual(UITextStorageDirectionForward, position.affinity);
1961 position = (FlutterTextPosition*)[inputView closestPositionToPoint:CGPointMake(225, 50)];
1962 XCTAssertEqual(1U, position.index);
1963 XCTAssertEqual(UITextStorageDirectionBackward, position.affinity);
1964 position = (FlutterTextPosition*)[inputView closestPositionToPoint:CGPointMake(175, 50)];
1965 XCTAssertEqual(1U, position.index);
1966 XCTAssertEqual(UITextStorageDirectionForward, position.affinity);
1967 position = (FlutterTextPosition*)[inputView closestPositionToPoint:CGPointMake(125, 50)];
1968 XCTAssertEqual(2U, position.index);
1969 XCTAssertEqual(UITextStorageDirectionBackward, position.affinity);
1970 position = (FlutterTextPosition*)[inputView closestPositionToPoint:CGPointMake(75, 50)];
1971 XCTAssertEqual(2U, position.index);
1972 XCTAssertEqual(UITextStorageDirectionForward, position.affinity);
1973 position = (FlutterTextPosition*)[inputView closestPositionToPoint:CGPointMake(25, 50)];
1974 XCTAssertEqual(3U, position.index);
1975 XCTAssertEqual(UITextStorageDirectionBackward, position.affinity);
1976 position = (FlutterTextPosition*)[inputView closestPositionToPoint:CGPointMake(-25, 50)];
1977 XCTAssertEqual(3U, position.index);
1978 XCTAssertEqual(UITextStorageDirectionBackward, position.affinity);
1979}
UITextStorageDirection affinity
instancetype selectionRectWithRect:position:writingDirection:(CGRect rect,[position] NSUInteger position,[writingDirection] NSWritingDirection writingDirection)

◆ testClosestPositionToPointWithinRange

- (void) testClosestPositionToPointWithinRange
implementation

Definition at line 92 of file FlutterTextInputPluginTest.mm.

2008 {
2009 FlutterTextInputView* inputView = [[FlutterTextInputView alloc] initWithOwner:textInputPlugin];
2010 [inputView setTextInputState:@{@"text" : @"COMPOSING"}];
2011
2012 // Do not return a position before the start of the range
2013 [inputView setSelectionRects:@[
2014 [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(0, 0, 100, 100) position:0U],
2015 [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(0, 100, 100, 100) position:1U],
2016 [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(100, 100, 100, 100) position:2U],
2017 [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(200, 100, 100, 100) position:3U],
2018 [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(0, 200, 100, 100) position:4U],
2019 ]];
2020 CGPoint point = CGPointMake(125, 150);
2021 FlutterTextRange* range = [[FlutterTextRange rangeWithNSRange:NSMakeRange(3, 2)] copy];
2022 XCTAssertEqual(
2023 3U, ((FlutterTextPosition*)[inputView closestPositionToPoint:point withinRange:range]).index);
2024 XCTAssertEqual(
2025 UITextStorageDirectionForward,
2026 ((FlutterTextPosition*)[inputView closestPositionToPoint:point withinRange:range]).affinity);
2027
2028 // Do not return a position after the end of the range
2029 [inputView setSelectionRects:@[
2030 [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(0, 0, 100, 100) position:0U],
2031 [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(0, 100, 100, 100) position:1U],
2032 [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(100, 100, 100, 100) position:2U],
2033 [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(200, 100, 100, 100) position:3U],
2034 [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(0, 200, 100, 100) position:4U],
2035 ]];
2036 point = CGPointMake(125, 150);
2037 range = [[FlutterTextRange rangeWithNSRange:NSMakeRange(0, 1)] copy];
2038 XCTAssertEqual(
2039 1U, ((FlutterTextPosition*)[inputView closestPositionToPoint:point withinRange:range]).index);
2040 XCTAssertEqual(
2041 UITextStorageDirectionForward,
2042 ((FlutterTextPosition*)[inputView closestPositionToPoint:point withinRange:range]).affinity);
2043}

◆ testClosestPositionToPointWithPartialSelectionRects

- (void) testClosestPositionToPointWithPartialSelectionRects
implementation

Definition at line 92 of file FlutterTextInputPluginTest.mm.

2045 {
2046 FlutterTextInputView* inputView = [[FlutterTextInputView alloc] initWithOwner:textInputPlugin];
2047 [inputView setTextInputState:@{@"text" : @"COMPOSING"}];
2048
2049 [inputView setSelectionRects:@[ [FlutterTextSelectionRect
2050 selectionRectWithRect:CGRectMake(0, 0, 100, 100)
2051 position:0U] ]];
2052 // Asking with a position at the end of selection rects should give you the trailing edge of
2053 // the last rect.
2054 XCTAssertTrue(CGRectEqualToRect(
2056 positionWithIndex:1
2057 affinity:UITextStorageDirectionForward]],
2058 CGRectMake(100, 0, 0, 100)));
2059 // Asking with a position beyond the end of selection rects should return CGRectZero without
2060 // crashing.
2061 XCTAssertTrue(CGRectEqualToRect(
2063 positionWithIndex:2
2064 affinity:UITextStorageDirectionForward]],
2065 CGRectZero));
2066}
CGRect caretRectForPosition

◆ testCommitAutofillContext

- (void) testCommitAutofillContext
implementation

Definition at line 92 of file FlutterTextInputPluginTest.mm.

2419 {
2420 NSMutableDictionary* field1 = self.mutableTemplateCopy;
2421 [field1 setValue:@{
2422 @"uniqueIdentifier" : @"field1",
2423 @"hints" : @[ @"hint1" ],
2424 @"editingValue" : @{@"text" : @""}
2425 }
2426 forKey:@"autofill"];
2427
2428 NSMutableDictionary* field2 = self.mutablePasswordTemplateCopy;
2429 [field2 setValue:@{
2430 @"uniqueIdentifier" : @"field2",
2431 @"hints" : @[ @"hint2" ],
2432 @"editingValue" : @{@"text" : @""}
2433 }
2434 forKey:@"autofill"];
2435
2436 NSMutableDictionary* field3 = self.mutableTemplateCopy;
2437 [field3 setValue:@{
2438 @"uniqueIdentifier" : @"field3",
2439 @"hints" : @[ @"hint3" ],
2440 @"editingValue" : @{@"text" : @""}
2441 }
2442 forKey:@"autofill"];
2443
2444 NSMutableDictionary* config = [field1 mutableCopy];
2445 [config setValue:@[ field1, field2 ] forKey:@"fields"];
2446
2447 [self setClientId:123 configuration:config];
2448 XCTAssertEqual(self.viewsVisibleToAutofill.count, 2ul);
2449 XCTAssertEqual(textInputPlugin.autofillContext.count, 2ul);
2450 [self ensureOnlyActiveViewCanBecomeFirstResponder];
2451
2452 [self commitAutofillContextAndVerify];
2453 [self ensureOnlyActiveViewCanBecomeFirstResponder];
2454
2455 // Install the password field again.
2456 [self setClientId:123 configuration:config];
2457 // Switch to a regular autofill group.
2458 [self setClientId:124 configuration:field3];
2459 XCTAssertEqual(self.viewsVisibleToAutofill.count, 1ul);
2460
2461 [textInputPlugin cleanUpViewHierarchy:NO clearText:YES delayRemoval:NO];
2462 XCTAssertEqual(self.installedInputViews.count, 3ul);
2463 XCTAssertEqual(textInputPlugin.autofillContext.count, 2ul);
2464 XCTAssertNotEqual(textInputPlugin.textInputView, nil);
2465 [self ensureOnlyActiveViewCanBecomeFirstResponder];
2466
2467 [self commitAutofillContextAndVerify];
2468 [self ensureOnlyActiveViewCanBecomeFirstResponder];
2469
2470 // Now switch to an input field that does not autofill.
2471 [self setClientId:125 configuration:self.mutableTemplateCopy];
2472
2473 XCTAssertEqual(self.viewsVisibleToAutofill.count, 0ul);
2474 // The active view should still be installed so it doesn't get
2475 // deallocated.
2476
2477 [textInputPlugin cleanUpViewHierarchy:NO clearText:YES delayRemoval:NO];
2478 XCTAssertEqual(self.installedInputViews.count, 1ul);
2479 XCTAssertEqual(textInputPlugin.autofillContext.count, 0ul);
2480 [self ensureOnlyActiveViewCanBecomeFirstResponder];
2481
2482 [self commitAutofillContextAndVerify];
2483 [self ensureOnlyActiveViewCanBecomeFirstResponder];
2484}

◆ testDecommissionedViewAreNotReusedByAutofill

- (void) testDecommissionedViewAreNotReusedByAutofill
implementation

Definition at line 92 of file FlutterTextInputPluginTest.mm.

2634 {
2635 // Regression test for https://github.com/flutter/flutter/issues/84407.
2636 NSMutableDictionary* configuration = self.mutableTemplateCopy;
2637 [configuration setValue:@{
2638 @"uniqueIdentifier" : @"field1",
2639 @"hints" : @[ UITextContentTypePassword ],
2640 @"editingValue" : @{@"text" : @""}
2641 }
2642 forKey:@"autofill"];
2643 [configuration setValue:@[ [configuration copy] ] forKey:@"fields"];
2644
2645 [self setClientId:123 configuration:configuration];
2646
2647 [self setTextInputHide];
2648 UIView* previousActiveView = textInputPlugin.activeView;
2649
2650 [self setClientId:124 configuration:configuration];
2651
2652 // Make sure the autofillable view is reused.
2653 XCTAssertEqual(previousActiveView, textInputPlugin.activeView);
2654 XCTAssertNotNil(previousActiveView);
2655 // Does not crash.
2656}

◆ testDeletingBackward

- (void) testDeletingBackward
implementation

Definition at line 92 of file FlutterTextInputPluginTest.mm.

530 {
531 NSDictionary* config = self.mutableTemplateCopy;
532 [self setClientId:123 configuration:config];
533 NSArray<FlutterTextInputView*>* inputFields = self.installedInputViews;
534 FlutterTextInputView* inputView = inputFields[0];
535
536 [inputView insertText:@"ឹ😀 text 🥰👨‍👩‍👧‍👦🇺🇳ดี "];
537 [inputView deleteBackward];
538 [inputView deleteBackward];
539
540 // Thai vowel is removed.
541 XCTAssertEqualObjects(inputView.text, @"ឹ😀 text 🥰👨‍👩‍👧‍👦🇺🇳ด");
542 [inputView deleteBackward];
543 XCTAssertEqualObjects(inputView.text, @"ឹ😀 text 🥰👨‍👩‍👧‍👦🇺🇳");
544 [inputView deleteBackward];
545 XCTAssertEqualObjects(inputView.text, @"ឹ😀 text 🥰👨‍👩‍👧‍👦");
546 [inputView deleteBackward];
547 XCTAssertEqualObjects(inputView.text, @"ឹ😀 text 🥰");
548 [inputView deleteBackward];
549
550 XCTAssertEqualObjects(inputView.text, @"ឹ😀 text ");
551 [inputView deleteBackward];
552 [inputView deleteBackward];
553 [inputView deleteBackward];
554 [inputView deleteBackward];
555 [inputView deleteBackward];
556 [inputView deleteBackward];
557
558 XCTAssertEqualObjects(inputView.text, @"ឹ😀");
559 [inputView deleteBackward];
560 XCTAssertEqualObjects(inputView.text, @"ឹ");
561 [inputView deleteBackward];
562 XCTAssertEqualObjects(inputView.text, @"");
563}

◆ testDisablingAutocorrectDisablesSpellChecking

- (void) testDisablingAutocorrectDisablesSpellChecking
implementation

Definition at line 92 of file FlutterTextInputPluginTest.mm.

786 {
787 FlutterTextInputView* inputView = [[FlutterTextInputView alloc] initWithOwner:textInputPlugin];
788
789 // Disable the interactive selection.
790 NSDictionary* config = self.mutableTemplateCopy;
791 [inputView configureWithDictionary:config];
792
793 XCTAssertEqual(inputView.autocorrectionType, UITextAutocorrectionTypeDefault);
794 XCTAssertEqual(inputView.spellCheckingType, UITextSpellCheckingTypeDefault);
795
796 [config setValue:@(NO) forKey:@"autocorrect"];
797 [inputView configureWithDictionary:config];
798
799 XCTAssertEqual(inputView.autocorrectionType, UITextAutocorrectionTypeNo);
800 XCTAssertEqual(inputView.spellCheckingType, UITextSpellCheckingTypeNo);
801}
void configureWithDictionary:(NSDictionary *configuration)

◆ testDisablingAutofillOnInputClient

- (void) testDisablingAutofillOnInputClient
implementation

Definition at line 92 of file FlutterTextInputPluginTest.mm.

2298 {
2299 NSDictionary* config = self.mutableTemplateCopy;
2300 [config setValue:@"YES" forKey:@"obscureText"];
2301
2302 [self setClientId:123 configuration:config];
2303
2304 FlutterTextInputView* inputView = self.installedInputViews[0];
2305 XCTAssertEqualObjects(inputView.textContentType, @"");
2306}

◆ testDoNotReuseInputViews

- (void) testDoNotReuseInputViews
implementation

Definition at line 92 of file FlutterTextInputPluginTest.mm.

661 {
662 NSDictionary* config = self.mutableTemplateCopy;
663 [self setClientId:123 configuration:config];
665 [self setClientId:456 configuration:config];
666
667 XCTAssertNotNil(currentView);
668 XCTAssertNotNil(textInputPlugin.activeView);
669 XCTAssertNotEqual(currentView, textInputPlugin.activeView);
670}

◆ testEditMenu_shouldNotPresentEditMenuIfNotFirstResponder

- (void) testEditMenu_shouldNotPresentEditMenuIfNotFirstResponder
implementation

Definition at line 92 of file FlutterTextInputPluginTest.mm.

2859 {
2860 if (@available(iOS 16.0, *)) {
2861 FlutterTextInputPlugin* myInputPlugin =
2862 [[FlutterTextInputPlugin alloc] initWithDelegate:OCMClassMock([FlutterEngine class])];
2863 BOOL shownEditMenu = [myInputPlugin showEditMenu:@{}];
2864 XCTAssertFalse(shownEditMenu, @"Should not show edit menu if not first responder.");
2865 }
2866}
BOOL showEditMenu:(ios(16.0) API_AVAILABLE)
int BOOL
Definition: windows_types.h:37

◆ testEditMenu_shouldPresentEditMenuWithCorectTargetRect

- (void) testEditMenu_shouldPresentEditMenuWithCorectTargetRect
implementation

Definition at line 92 of file FlutterTextInputPluginTest.mm.

2914 {
2915 if (@available(iOS 16.0, *)) {
2916 FlutterTextInputPlugin* myInputPlugin =
2917 [[FlutterTextInputPlugin alloc] initWithDelegate:OCMClassMock([FlutterEngine class])];
2918 FlutterViewController* myViewController = [[FlutterViewController alloc] init];
2919 myInputPlugin.viewController = myViewController;
2920 [myViewController loadView];
2921
2922 FlutterMethodCall* setClientCall =
2923 [FlutterMethodCall methodCallWithMethodName:@"TextInput.setClient"
2924 arguments:@[ @(123), self.mutableTemplateCopy ]];
2925 [myInputPlugin handleMethodCall:setClientCall
2926 result:^(id _Nullable result){
2927 }];
2928
2929 FlutterTextInputView* myInputView = myInputPlugin.activeView;
2930
2931 FlutterTextInputView* mockInputView = OCMPartialMock(myInputView);
2932 OCMStub([mockInputView isFirstResponder]).andReturn(YES);
2933
2934 XCTestExpectation* expectation = [[XCTestExpectation alloc]
2935 initWithDescription:@"presentEditMenuWithConfiguration must be called."];
2936
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];
2942 });
2943
2944 myInputView.frame = CGRectMake(10, 20, 30, 40);
2945 NSDictionary<NSString*, NSNumber*>* encodedTargetRect =
2946 @{@"x" : @(100), @"y" : @(200), @"width" : @(300), @"height" : @(400)};
2947
2948 BOOL shownEditMenu = [myInputPlugin showEditMenu:@{@"targetRect" : encodedTargetRect}];
2949 XCTAssertTrue(shownEditMenu, @"Should show edit menu with correct configuration.");
2950 [self waitForExpectations:@[ expectation ] timeout:1.0];
2951
2952 CGRect targetRect =
2953 [myInputView editMenuInteraction:mockInteraction
2954 targetRectForConfiguration:OCMClassMock([UIEditMenuConfiguration class])];
2955 // the encoded target rect is in global coordinate space.
2956 XCTAssert(CGRectEqualToRect(targetRect, CGRectMake(90, 180, 300, 400)),
2957 @"targetRectForConfiguration must return the correct target rect.");
2958 }
2959}
CGRect editMenuInteraction:targetRectForConfiguration:(UIEditMenuInteraction *interaction, [targetRectForConfiguration] ios(16.0) API_AVAILABLE)
void handleMethodCall:result:(FlutterMethodCall *call,[result] FlutterResult result)
SIT bool any(const Vec< 1, T > &x)
Definition: SkVx.h:530

◆ testEditMenu_shouldPresentEditMenuWithCorrectConfiguration

- (void) testEditMenu_shouldPresentEditMenuWithCorrectConfiguration
implementation

Definition at line 92 of file FlutterTextInputPluginTest.mm.

2868 {
2869 if (@available(iOS 16.0, *)) {
2870 FlutterTextInputPlugin* myInputPlugin =
2871 [[FlutterTextInputPlugin alloc] initWithDelegate:OCMClassMock([FlutterEngine class])];
2872 FlutterViewController* myViewController = [[FlutterViewController alloc] init];
2873 myInputPlugin.viewController = myViewController;
2874 [myViewController loadView];
2875 FlutterMethodCall* setClientCall =
2876 [FlutterMethodCall methodCallWithMethodName:@"TextInput.setClient"
2877 arguments:@[ @(123), self.mutableTemplateCopy ]];
2878 [myInputPlugin handleMethodCall:setClientCall
2879 result:^(id _Nullable result){
2880 }];
2881
2882 FlutterTextInputView* myInputView = myInputPlugin.activeView;
2883 FlutterTextInputView* mockInputView = OCMPartialMock(myInputView);
2884
2885 OCMStub([mockInputView isFirstResponder]).andReturn(YES);
2886
2887 XCTestExpectation* expectation = [[XCTestExpectation alloc]
2888 initWithDescription:@"presentEditMenuWithConfiguration must be called."];
2889
2890 id mockInteraction = OCMClassMock([UIEditMenuInteraction class]);
2891 OCMStub([mockInputView editMenuInteraction]).andReturn(mockInteraction);
2892 OCMStub([mockInteraction presentEditMenuWithConfiguration:[OCMArg any]])
2893 .andDo(^(NSInvocation* invocation) {
2894 // arguments are released once invocation is released.
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];
2903 });
2904
2905 NSDictionary<NSString*, NSNumber*>* encodedTargetRect =
2906 @{@"x" : @(0), @"y" : @(0), @"width" : @(0), @"height" : @(0)};
2907
2908 BOOL shownEditMenu = [myInputPlugin showEditMenu:@{@"targetRect" : encodedTargetRect}];
2909 XCTAssertTrue(shownEditMenu, @"Should show edit menu with correct configuration.");
2910 [self waitForExpectations:@[ expectation ] timeout:1.0];
2911 }
2912}

◆ testEditMenu_shouldSetupEditMenuDelegateCorrectly

- (void) testEditMenu_shouldSetupEditMenuDelegateCorrectly
implementation

Definition at line 92 of file FlutterTextInputPluginTest.mm.

2850 {
2851 if (@available(iOS 16.0, *)) {
2852 FlutterTextInputView* inputView = [[FlutterTextInputView alloc] initWithOwner:textInputPlugin];
2853 [UIApplication.sharedApplication.keyWindow addSubview:inputView];
2854 XCTAssertEqual(inputView.editMenuInteraction.delegate, inputView,
2855 @"editMenuInteraction setup delegate correctly");
2856 }
2857}

◆ testFirstRectForRangeReturnsCorrectRectOnASingleLineLeftToRight

- (void) testFirstRectForRangeReturnsCorrectRectOnASingleLineLeftToRight
implementation

Definition at line 92 of file FlutterTextInputPluginTest.mm.

1600 {
1601 FlutterTextInputView* inputView = [[FlutterTextInputView alloc] initWithOwner:textInputPlugin];
1602 [inputView setTextInputState:@{@"text" : @"COMPOSING"}];
1603
1604 [inputView setSelectionRects:@[
1605 [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(0, 0, 100, 100) position:0U],
1606 [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(100, 0, 100, 100) position:1U],
1607 [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(200, 0, 100, 100) position:2U],
1608 [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(300, 0, 100, 100) position:3U],
1609 ]];
1610 FlutterTextRange* singleRectRange = [FlutterTextRange rangeWithNSRange:NSMakeRange(1, 1)];
1611 if (@available(iOS 17, *)) {
1612 XCTAssertTrue(CGRectEqualToRect(CGRectMake(100, 0, 100, 100),
1613 [inputView firstRectForRange:singleRectRange]));
1614 } else {
1615 XCTAssertTrue(CGRectEqualToRect(CGRectZero, [inputView firstRectForRange:singleRectRange]));
1616 }
1617
1618 FlutterTextRange* multiRectRange = [FlutterTextRange rangeWithNSRange:NSMakeRange(1, 3)];
1619
1620 if (@available(iOS 17, *)) {
1621 XCTAssertTrue(CGRectEqualToRect(CGRectMake(100, 0, 300, 100),
1622 [inputView firstRectForRange:multiRectRange]));
1623 } else {
1624 XCTAssertTrue(CGRectEqualToRect(CGRectZero, [inputView firstRectForRange:multiRectRange]));
1625 }
1626
1627 [inputView setTextInputState:@{@"text" : @"COM"}];
1628 FlutterTextRange* rangeOutsideBounds = [FlutterTextRange rangeWithNSRange:NSMakeRange(3, 1)];
1629 XCTAssertTrue(CGRectEqualToRect(CGRectZero, [inputView firstRectForRange:rangeOutsideBounds]));
1630}

◆ testFirstRectForRangeReturnsCorrectRectOnASingleLineRightToLeft

- (void) testFirstRectForRangeReturnsCorrectRectOnASingleLineRightToLeft
implementation

Definition at line 92 of file FlutterTextInputPluginTest.mm.

1632 {
1633 FlutterTextInputView* inputView = [[FlutterTextInputView alloc] initWithOwner:textInputPlugin];
1634 [inputView setTextInputState:@{@"text" : @"COMPOSING"}];
1635
1636 [inputView setSelectionRects:@[
1637 [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(300, 0, 100, 100) position:0U],
1638 [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(200, 0, 100, 100) position:1U],
1639 [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(100, 0, 100, 100) position:2U],
1640 [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(0, 0, 100, 100) position:3U],
1641 ]];
1642 FlutterTextRange* singleRectRange = [FlutterTextRange rangeWithNSRange:NSMakeRange(1, 1)];
1643 if (@available(iOS 17, *)) {
1644 XCTAssertTrue(CGRectEqualToRect(CGRectMake(200, 0, 100, 100),
1645 [inputView firstRectForRange:singleRectRange]));
1646 } else {
1647 XCTAssertTrue(CGRectEqualToRect(CGRectZero, [inputView firstRectForRange:singleRectRange]));
1648 }
1649
1650 FlutterTextRange* multiRectRange = [FlutterTextRange rangeWithNSRange:NSMakeRange(1, 3)];
1651 if (@available(iOS 17, *)) {
1652 XCTAssertTrue(CGRectEqualToRect(CGRectMake(0, 0, 300, 100),
1653 [inputView firstRectForRange:multiRectRange]));
1654 } else {
1655 XCTAssertTrue(CGRectEqualToRect(CGRectZero, [inputView firstRectForRange:multiRectRange]));
1656 }
1657
1658 [inputView setTextInputState:@{@"text" : @"COM"}];
1659 FlutterTextRange* rangeOutsideBounds = [FlutterTextRange rangeWithNSRange:NSMakeRange(3, 1)];
1660 XCTAssertTrue(CGRectEqualToRect(CGRectZero, [inputView firstRectForRange:rangeOutsideBounds]));
1661}

◆ testFirstRectForRangeReturnsCorrectRectOnMultipleLinesLeftToRight

- (void) testFirstRectForRangeReturnsCorrectRectOnMultipleLinesLeftToRight
implementation

Definition at line 92 of file FlutterTextInputPluginTest.mm.

1663 {
1664 FlutterTextInputView* inputView = [[FlutterTextInputView alloc] initWithOwner:textInputPlugin];
1665 [inputView setTextInputState:@{@"text" : @"COMPOSING"}];
1666
1667 [inputView setSelectionRects:@[
1668 [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(0, 0, 100, 100) position:0U],
1669 [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(100, 0, 100, 100) position:1U],
1670 [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(200, 0, 100, 100) position:2U],
1671 [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(300, 0, 100, 100) position:3U],
1672 [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(0, 100, 100, 100) position:4U],
1673 [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(100, 100, 100, 100) position:5U],
1674 [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(200, 100, 100, 100) position:6U],
1675 [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(300, 100, 100, 100) position:7U],
1676 ]];
1677 FlutterTextRange* singleRectRange = [FlutterTextRange rangeWithNSRange:NSMakeRange(1, 1)];
1678 if (@available(iOS 17, *)) {
1679 XCTAssertTrue(CGRectEqualToRect(CGRectMake(100, 0, 100, 100),
1680 [inputView firstRectForRange:singleRectRange]));
1681 } else {
1682 XCTAssertTrue(CGRectEqualToRect(CGRectZero, [inputView firstRectForRange:singleRectRange]));
1683 }
1684
1685 FlutterTextRange* multiRectRange = [FlutterTextRange rangeWithNSRange:NSMakeRange(1, 4)];
1686
1687 if (@available(iOS 17, *)) {
1688 XCTAssertTrue(CGRectEqualToRect(CGRectMake(100, 0, 300, 100),
1689 [inputView firstRectForRange:multiRectRange]));
1690 } else {
1691 XCTAssertTrue(CGRectEqualToRect(CGRectZero, [inputView firstRectForRange:multiRectRange]));
1692 }
1693}

◆ testFirstRectForRangeReturnsCorrectRectOnMultipleLinesRightToLeft

- (void) testFirstRectForRangeReturnsCorrectRectOnMultipleLinesRightToLeft
implementation

Definition at line 92 of file FlutterTextInputPluginTest.mm.

1695 {
1696 FlutterTextInputView* inputView = [[FlutterTextInputView alloc] initWithOwner:textInputPlugin];
1697 [inputView setTextInputState:@{@"text" : @"COMPOSING"}];
1698
1699 [inputView setSelectionRects:@[
1700 [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(300, 0, 100, 100) position:0U],
1701 [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(200, 0, 100, 100) position:1U],
1702 [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(100, 0, 100, 100) position:2U],
1703 [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(0, 0, 100, 100) position:3U],
1704 [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(300, 100, 100, 100) position:4U],
1705 [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(200, 100, 100, 100) position:5U],
1706 [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(100, 100, 100, 100) position:6U],
1707 [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(0, 100, 100, 100) position:7U],
1708 ]];
1709 FlutterTextRange* singleRectRange = [FlutterTextRange rangeWithNSRange:NSMakeRange(1, 1)];
1710 if (@available(iOS 17, *)) {
1711 XCTAssertTrue(CGRectEqualToRect(CGRectMake(200, 0, 100, 100),
1712 [inputView firstRectForRange:singleRectRange]));
1713 } else {
1714 XCTAssertTrue(CGRectEqualToRect(CGRectZero, [inputView firstRectForRange:singleRectRange]));
1715 }
1716
1717 FlutterTextRange* multiRectRange = [FlutterTextRange rangeWithNSRange:NSMakeRange(1, 4)];
1718 if (@available(iOS 17, *)) {
1719 XCTAssertTrue(CGRectEqualToRect(CGRectMake(0, 0, 300, 100),
1720 [inputView firstRectForRange:multiRectRange]));
1721 } else {
1722 XCTAssertTrue(CGRectEqualToRect(CGRectZero, [inputView firstRectForRange:multiRectRange]));
1723 }
1724}

◆ testFirstRectForRangeReturnsCorrectRectOnSingleLineWithVaryingMinYAndMaxYLeftToRight

- (void) testFirstRectForRangeReturnsCorrectRectOnSingleLineWithVaryingMinYAndMaxYLeftToRight
implementation

Definition at line 92 of file FlutterTextInputPluginTest.mm.

1726 {
1727 FlutterTextInputView* inputView = [[FlutterTextInputView alloc] initWithOwner:textInputPlugin];
1728 [inputView setTextInputState:@{@"text" : @"COMPOSING"}];
1729
1730 [inputView setSelectionRects:@[
1731 [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(0, 0, 100, 100) position:0U],
1732 [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(100, 10, 100, 80)
1733 position:1U], // shorter
1734 [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(200, -10, 100, 120)
1735 position:2U], // taller
1736 [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(300, 0, 100, 100) position:3U],
1737 ]];
1738
1739 FlutterTextRange* multiRectRange = [FlutterTextRange rangeWithNSRange:NSMakeRange(1, 3)];
1740
1741 if (@available(iOS 17, *)) {
1742 XCTAssertTrue(CGRectEqualToRect(CGRectMake(100, -10, 300, 120),
1743 [inputView firstRectForRange:multiRectRange]));
1744 } else {
1745 XCTAssertTrue(CGRectEqualToRect(CGRectZero, [inputView firstRectForRange:multiRectRange]));
1746 }
1747}

◆ testFirstRectForRangeReturnsCorrectRectOnSingleLineWithVaryingMinYAndMaxYRightToLeft

- (void) testFirstRectForRangeReturnsCorrectRectOnSingleLineWithVaryingMinYAndMaxYRightToLeft
implementation

Definition at line 92 of file FlutterTextInputPluginTest.mm.

1749 {
1750 FlutterTextInputView* inputView = [[FlutterTextInputView alloc] initWithOwner:textInputPlugin];
1751 [inputView setTextInputState:@{@"text" : @"COMPOSING"}];
1752
1753 [inputView setSelectionRects:@[
1754 [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(300, 0, 100, 100) position:0U],
1755 [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(200, -10, 100, 120)
1756 position:1U], // taller
1757 [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(100, 10, 100, 80)
1758 position:2U], // shorter
1759 [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(0, 0, 100, 100) position:3U],
1760 ]];
1761
1762 FlutterTextRange* multiRectRange = [FlutterTextRange rangeWithNSRange:NSMakeRange(1, 3)];
1763
1764 if (@available(iOS 17, *)) {
1765 XCTAssertTrue(CGRectEqualToRect(CGRectMake(0, -10, 300, 120),
1766 [inputView firstRectForRange:multiRectRange]));
1767 } else {
1768 XCTAssertTrue(CGRectEqualToRect(CGRectZero, [inputView firstRectForRange:multiRectRange]));
1769 }
1770}

◆ testFirstRectForRangeReturnsCorrectRectWithOverlappingRectsExceedingThresholdLeftToRight

- (void) testFirstRectForRangeReturnsCorrectRectWithOverlappingRectsExceedingThresholdLeftToRight
implementation

Definition at line 92 of file FlutterTextInputPluginTest.mm.

1772 {
1773 FlutterTextInputView* inputView = [[FlutterTextInputView alloc] initWithOwner:textInputPlugin];
1774 [inputView setTextInputState:@{@"text" : @"COMPOSING"}];
1775
1776 [inputView setSelectionRects:@[
1777 [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(0, 0, 100, 100) position:0U],
1778 [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(100, 0, 100, 100) position:1U],
1779 [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(200, 0, 100, 100) position:2U],
1780 [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(300, 0, 100, 100) position:3U],
1781 // y=60 exceeds threshold, so treat it as a new line.
1782 [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(0, 60, 100, 100) position:4U],
1783 ]];
1784
1785 FlutterTextRange* multiRectRange = [FlutterTextRange rangeWithNSRange:NSMakeRange(1, 4)];
1786
1787 if (@available(iOS 17, *)) {
1788 XCTAssertTrue(CGRectEqualToRect(CGRectMake(100, 0, 300, 100),
1789 [inputView firstRectForRange:multiRectRange]));
1790 } else {
1791 XCTAssertTrue(CGRectEqualToRect(CGRectZero, [inputView firstRectForRange:multiRectRange]));
1792 }
1793}

◆ testFirstRectForRangeReturnsCorrectRectWithOverlappingRectsExceedingThresholdRightToLeft

- (void) testFirstRectForRangeReturnsCorrectRectWithOverlappingRectsExceedingThresholdRightToLeft
implementation

Definition at line 92 of file FlutterTextInputPluginTest.mm.

1795 {
1796 FlutterTextInputView* inputView = [[FlutterTextInputView alloc] initWithOwner:textInputPlugin];
1797 [inputView setTextInputState:@{@"text" : @"COMPOSING"}];
1798
1799 [inputView setSelectionRects:@[
1800 [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(300, 0, 100, 100) position:0U],
1801 [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(200, 0, 100, 100) position:1U],
1802 [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(100, 0, 100, 100) position:2U],
1803 [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(0, 0, 100, 100) position:3U],
1804 // y=60 exceeds threshold, so treat it as a new line.
1805 [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(300, 60, 100, 100) position:4U],
1806 ]];
1807
1808 FlutterTextRange* multiRectRange = [FlutterTextRange rangeWithNSRange:NSMakeRange(1, 4)];
1809
1810 if (@available(iOS 17, *)) {
1811 XCTAssertTrue(CGRectEqualToRect(CGRectMake(0, 0, 300, 100),
1812 [inputView firstRectForRange:multiRectRange]));
1813 } else {
1814 XCTAssertTrue(CGRectEqualToRect(CGRectZero, [inputView firstRectForRange:multiRectRange]));
1815 }
1816}

◆ testFirstRectForRangeReturnsCorrectRectWithOverlappingRectsWithinThresholdLeftToRight

- (void) testFirstRectForRangeReturnsCorrectRectWithOverlappingRectsWithinThresholdLeftToRight
implementation

Definition at line 92 of file FlutterTextInputPluginTest.mm.

1818 {
1819 FlutterTextInputView* inputView = [[FlutterTextInputView alloc] initWithOwner:textInputPlugin];
1820 [inputView setTextInputState:@{@"text" : @"COMPOSING"}];
1821
1822 [inputView setSelectionRects:@[
1823 [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(0, 0, 100, 100) position:0U],
1824 [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(100, 0, 100, 100) position:1U],
1825 [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(200, 0, 100, 100) position:2U],
1826 [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(300, 0, 100, 100) position:3U],
1827 // y=40 is within line threshold, so treat it as the same line
1828 [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(400, 40, 100, 100) position:4U],
1829 ]];
1830
1831 FlutterTextRange* multiRectRange = [FlutterTextRange rangeWithNSRange:NSMakeRange(1, 4)];
1832
1833 if (@available(iOS 17, *)) {
1834 XCTAssertTrue(CGRectEqualToRect(CGRectMake(100, 0, 400, 140),
1835 [inputView firstRectForRange:multiRectRange]));
1836 } else {
1837 XCTAssertTrue(CGRectEqualToRect(CGRectZero, [inputView firstRectForRange:multiRectRange]));
1838 }
1839}

◆ testFirstRectForRangeReturnsCorrectRectWithOverlappingRectsWithinThresholdRightToLeft

- (void) testFirstRectForRangeReturnsCorrectRectWithOverlappingRectsWithinThresholdRightToLeft
implementation

Definition at line 92 of file FlutterTextInputPluginTest.mm.

1841 {
1842 FlutterTextInputView* inputView = [[FlutterTextInputView alloc] initWithOwner:textInputPlugin];
1843 [inputView setTextInputState:@{@"text" : @"COMPOSING"}];
1844
1845 [inputView setSelectionRects:@[
1846 [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(400, 0, 100, 100) position:0U],
1847 [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(300, 0, 100, 100) position:1U],
1848 [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(200, 0, 100, 100) position:2U],
1849 [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(100, 0, 100, 100) position:3U],
1850 // y=40 is within line threshold, so treat it as the same line
1851 [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(0, 40, 100, 100) position:4U],
1852 ]];
1853
1854 FlutterTextRange* multiRectRange = [FlutterTextRange rangeWithNSRange:NSMakeRange(1, 4)];
1855
1856 if (@available(iOS 17, *)) {
1857 XCTAssertTrue(CGRectEqualToRect(CGRectMake(0, 0, 400, 140),
1858 [inputView firstRectForRange:multiRectRange]));
1859 } else {
1860 XCTAssertTrue(CGRectEqualToRect(CGRectZero, [inputView firstRectForRange:multiRectRange]));
1861 }
1862}

◆ testFirstRectForRangeReturnsNoneZeroRectWhenScribbleIsEnabled

- (void) testFirstRectForRangeReturnsNoneZeroRectWhenScribbleIsEnabled
implementation

Definition at line 92 of file FlutterTextInputPluginTest.mm.

1575 {
1576 FlutterTextInputView* inputView = [[FlutterTextInputView alloc] initWithOwner:textInputPlugin];
1577 [inputView setTextInputState:@{@"text" : @"COMPOSING"}];
1578
1579 FlutterTextInputView* mockInputView = OCMPartialMock(inputView);
1580 OCMStub([mockInputView isScribbleAvailable]).andReturn(YES);
1581
1582 [inputView setSelectionRects:@[
1583 [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(0, 0, 100, 100) position:0U],
1584 [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(100, 0, 100, 100) position:1U],
1585 [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(200, 0, 100, 100) position:2U],
1586 [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(300, 0, 100, 100) position:3U],
1587 ]];
1588
1589 FlutterTextRange* multiRectRange = [FlutterTextRange rangeWithNSRange:NSMakeRange(1, 3)];
1590
1591 if (@available(iOS 17, *)) {
1592 XCTAssertTrue(CGRectEqualToRect(CGRectMake(100, 0, 300, 100),
1593 [inputView firstRectForRange:multiRectRange]));
1594 } else {
1595 XCTAssertTrue(CGRectEqualToRect(CGRectMake(100, 0, 100, 100),
1596 [inputView firstRectForRange:multiRectRange]));
1597 }
1598}

◆ testFloatingCursor

- (void) testFloatingCursor
implementation

Definition at line 92 of file FlutterTextInputPluginTest.mm.

2080 {
2081 FlutterTextInputView* inputView = [[FlutterTextInputView alloc] initWithOwner:textInputPlugin];
2082 [inputView setTextInputState:@{
2083 @"text" : @"test",
2084 @"selectionBase" : @1,
2085 @"selectionExtent" : @1,
2086 }];
2087
2089 [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(0, 0, 100, 100) position:0U];
2090 FlutterTextSelectionRect* second =
2091 [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(100, 100, 100, 100) position:1U];
2093 [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(200, 200, 100, 100) position:2U];
2094 FlutterTextSelectionRect* fourth =
2095 [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(300, 300, 100, 100) position:3U];
2096 [inputView setSelectionRects:@[ first, second, third, fourth ]];
2097
2098 // Verify zeroth caret rect is based on left edge of first character.
2099 XCTAssertTrue(CGRectEqualToRect(
2101 positionWithIndex:0
2102 affinity:UITextStorageDirectionForward]],
2103 CGRectMake(0, 0, 0, 100)));
2104 // Since the textAffinity is downstream, the caret rect will be based on the
2105 // left edge of the succeeding character.
2106 XCTAssertTrue(CGRectEqualToRect(
2108 positionWithIndex:1
2109 affinity:UITextStorageDirectionForward]],
2110 CGRectMake(100, 100, 0, 100)));
2111 XCTAssertTrue(CGRectEqualToRect(
2113 positionWithIndex:2
2114 affinity:UITextStorageDirectionForward]],
2115 CGRectMake(200, 200, 0, 100)));
2116 XCTAssertTrue(CGRectEqualToRect(
2118 positionWithIndex:3
2119 affinity:UITextStorageDirectionForward]],
2120 CGRectMake(300, 300, 0, 100)));
2121 // There is no subsequent character for the last position, so the caret rect
2122 // will be based on the right edge of the preceding character.
2123 XCTAssertTrue(CGRectEqualToRect(
2125 positionWithIndex:4
2126 affinity:UITextStorageDirectionForward]],
2127 CGRectMake(400, 300, 0, 100)));
2128 // Verify no caret rect for out-of-range character.
2129 XCTAssertTrue(CGRectEqualToRect(
2131 positionWithIndex:5
2132 affinity:UITextStorageDirectionForward]],
2133 CGRectZero));
2134
2135 // Check caret rects again again when text affinity is upstream.
2136 [inputView setTextInputState:@{
2137 @"text" : @"test",
2138 @"selectionBase" : @2,
2139 @"selectionExtent" : @2,
2140 }];
2141 // Verify zeroth caret rect is based on left edge of first character.
2142 XCTAssertTrue(CGRectEqualToRect(
2144 positionWithIndex:0
2145 affinity:UITextStorageDirectionBackward]],
2146 CGRectMake(0, 0, 0, 100)));
2147 // Since the textAffinity is upstream, all below caret rects will be based on
2148 // the right edge of the preceding character.
2149 XCTAssertTrue(CGRectEqualToRect(
2151 positionWithIndex:1
2152 affinity:UITextStorageDirectionBackward]],
2153 CGRectMake(100, 0, 0, 100)));
2154 XCTAssertTrue(CGRectEqualToRect(
2156 positionWithIndex:2
2157 affinity:UITextStorageDirectionBackward]],
2158 CGRectMake(200, 100, 0, 100)));
2159 XCTAssertTrue(CGRectEqualToRect(
2161 positionWithIndex:3
2162 affinity:UITextStorageDirectionBackward]],
2163 CGRectMake(300, 200, 0, 100)));
2164 XCTAssertTrue(CGRectEqualToRect(
2166 positionWithIndex:4
2167 affinity:UITextStorageDirectionBackward]],
2168 CGRectMake(400, 300, 0, 100)));
2169 // Verify no caret rect for out-of-range character.
2170 XCTAssertTrue(CGRectEqualToRect(
2172 positionWithIndex:5
2173 affinity:UITextStorageDirectionBackward]],
2174 CGRectZero));
2175
2176 // Verify floating cursor updates are relative to original position, and that there is no bounds
2177 // change.
2178 CGRect initialBounds = inputView.bounds;
2179 [inputView beginFloatingCursorAtPoint:CGPointMake(123, 321)];
2180 XCTAssertTrue(CGRectEqualToRect(initialBounds, inputView.bounds));
2181 OCMVerify([engine flutterTextInputView:inputView
2182 updateFloatingCursor:FlutterFloatingCursorDragStateStart
2183 withClient:0
2184 withPosition:[OCMArg checkWithBlock:^BOOL(NSDictionary* state) {
2185 return ([state[@"X"] isEqualToNumber:@(0)]) &&
2186 ([state[@"Y"] isEqualToNumber:@(0)]);
2187 }]]);
2188
2189 [inputView updateFloatingCursorAtPoint:CGPointMake(456, 654)];
2190 XCTAssertTrue(CGRectEqualToRect(initialBounds, inputView.bounds));
2191 OCMVerify([engine flutterTextInputView:inputView
2192 updateFloatingCursor:FlutterFloatingCursorDragStateUpdate
2193 withClient:0
2194 withPosition:[OCMArg checkWithBlock:^BOOL(NSDictionary* state) {
2195 return ([state[@"X"] isEqualToNumber:@(333)]) &&
2196 ([state[@"Y"] isEqualToNumber:@(333)]);
2197 }]]);
2198
2199 [inputView endFloatingCursor];
2200 XCTAssertTrue(CGRectEqualToRect(initialBounds, inputView.bounds));
2201 OCMVerify([engine flutterTextInputView:inputView
2202 updateFloatingCursor:FlutterFloatingCursorDragStateEnd
2203 withClient:0
2204 withPosition:[OCMArg checkWithBlock:^BOOL(NSDictionary* state) {
2205 return ([state[@"X"] isEqualToNumber:@(0)]) &&
2206 ([state[@"Y"] isEqualToNumber:@(0)]);
2207 }]]);
2208}
void updateFloatingCursorAtPoint:(CGPoint point)
AtkStateType state

◆ testFloatingCursorDoesNotThrow

- (void) testFloatingCursorDoesNotThrow
implementation

Definition at line 92 of file FlutterTextInputPluginTest.mm.

2070 {
2071 // The keyboard implementation may send unbalanced calls to the input view.
2072 FlutterTextInputView* inputView = [[FlutterTextInputView alloc] initWithOwner:textInputPlugin];
2073 [inputView beginFloatingCursorAtPoint:CGPointMake(123, 321)];
2074 [inputView beginFloatingCursorAtPoint:CGPointMake(123, 321)];
2075 [inputView endFloatingCursor];
2076 [inputView beginFloatingCursorAtPoint:CGPointMake(123, 321)];
2077 [inputView endFloatingCursor];
2078}

◆ testFlutterTextInputPluginHostViewNilCrash

- (void) testFlutterTextInputPluginHostViewNilCrash
implementation

Definition at line 92 of file FlutterTextInputPluginTest.mm.

2813 {
2814 FlutterTextInputPlugin* myInputPlugin = [[FlutterTextInputPlugin alloc] initWithDelegate:engine];
2815 myInputPlugin.viewController = nil;
2816 XCTAssertThrows([myInputPlugin hostView], @"Throws exception if host view is nil");
2817}

◆ testFlutterTextInputPluginHostViewNotNil

- (void) testFlutterTextInputPluginHostViewNotNil
implementation

Definition at line 92 of file FlutterTextInputPluginTest.mm.

2819 {
2820 FlutterViewController* flutterViewController = [[FlutterViewController alloc] init];
2821 FlutterEngine* flutterEngine = [[FlutterEngine alloc] init];
2822 [flutterEngine runWithEntrypoint:nil];
2823 flutterEngine.viewController = flutterViewController;
2824 XCTAssertNotNil(flutterEngine.textInputPlugin.viewController);
2825 XCTAssertNotNil([flutterEngine.textInputPlugin hostView]);
2826}
BOOL runWithEntrypoint:(nullable NSString *entrypoint)
FlutterTextInputPlugin * textInputPlugin()
FlutterViewController * viewController

◆ testFlutterTextInputPluginRetainsFlutterTextInputView

- (void) testFlutterTextInputPluginRetainsFlutterTextInputView
implementation

Definition at line 92 of file FlutterTextInputPluginTest.mm.

2786 {
2787 FlutterViewController* flutterViewController = [[FlutterViewController alloc] init];
2788 FlutterTextInputPlugin* myInputPlugin = [[FlutterTextInputPlugin alloc] initWithDelegate:engine];
2789 myInputPlugin.viewController = flutterViewController;
2790
2791 __weak UIView* activeView;
2792 @autoreleasepool {
2793 FlutterMethodCall* setClientCall = [FlutterMethodCall
2794 methodCallWithMethodName:@"TextInput.setClient"
2795 arguments:@[
2796 [NSNumber numberWithInt:123], self.mutablePasswordTemplateCopy
2797 ]];
2798 [myInputPlugin handleMethodCall:setClientCall
2799 result:^(id _Nullable result){
2800 }];
2801 activeView = myInputPlugin.textInputView;
2802 FlutterMethodCall* hideCall = [FlutterMethodCall methodCallWithMethodName:@"TextInput.hide"
2803 arguments:@[]];
2804 [myInputPlugin handleMethodCall:hideCall
2805 result:^(id _Nullable result){
2806 }];
2807 XCTAssertNotNil(activeView);
2808 }
2809 // This assert proves the myInputPlugin.textInputView is not deallocated.
2810 XCTAssertNotNil(activeView);
2811}

◆ testFlutterTextInputViewDirectFocusToBackingTextInput

- (void) testFlutterTextInputViewDirectFocusToBackingTextInput
implementation

Definition at line 92 of file FlutterTextInputPluginTest.mm.

2687 {
2688 FlutterTextInputViewSpy* inputView =
2689 [[FlutterTextInputViewSpy alloc] initWithOwner:textInputPlugin];
2690 UIView* container = [[UIView alloc] init];
2691 UIAccessibilityElement* backing =
2692 [[UIAccessibilityElement alloc] initWithAccessibilityContainer:container];
2693 inputView.backingTextInputAccessibilityObject = backing;
2694 // Simulate accessibility focus.
2695 inputView.isAccessibilityFocused = YES;
2697
2698 XCTAssertEqual(inputView.receivedNotification, UIAccessibilityScreenChangedNotification);
2699 XCTAssertEqual(inputView.receivedNotificationTarget, backing);
2700}
UIAccessibilityNotifications receivedNotification

◆ testFlutterTextInputViewOnlyRespondsToInsertionPointColorBelowIOS17

- (void) testFlutterTextInputViewOnlyRespondsToInsertionPointColorBelowIOS17
implementation

Definition at line 92 of file FlutterTextInputPluginTest.mm.

822 {
823 FlutterTextInputView* inputView = [[FlutterTextInputView alloc] initWithOwner:textInputPlugin];
824 BOOL respondsToInsertionPointColor =
825 [inputView respondsToSelector:@selector(insertionPointColor)];
826 if (@available(iOS 17, *)) {
827 XCTAssertFalse(respondsToInsertionPointColor);
828 } else {
829 XCTAssertTrue(respondsToInsertionPointColor);
830 }
831}
BOOL respondsToSelector:(SEL selector)

◆ testFlutterTokenizerCanParseLines

- (void) testFlutterTokenizerCanParseLines
implementation

Definition at line 92 of file FlutterTextInputPluginTest.mm.

2702 {
2703 FlutterTextInputView* inputView = [[FlutterTextInputView alloc] initWithOwner:textInputPlugin];
2704 id<UITextInputTokenizer> tokenizer = [inputView tokenizer];
2705
2706 // The tokenizer returns zero range When text is empty.
2707 FlutterTextRange* range = [self getLineRangeFromTokenizer:tokenizer atIndex:0];
2708 XCTAssertEqual(range.range.location, 0u);
2709 XCTAssertEqual(range.range.length, 0u);
2710
2711 [inputView insertText:@"how are you\nI am fine, Thank you"];
2712
2713 range = [self getLineRangeFromTokenizer:tokenizer atIndex:0];
2714 XCTAssertEqual(range.range.location, 0u);
2715 XCTAssertEqual(range.range.length, 11u);
2716
2717 range = [self getLineRangeFromTokenizer:tokenizer atIndex:2];
2718 XCTAssertEqual(range.range.location, 0u);
2719 XCTAssertEqual(range.range.length, 11u);
2720
2721 range = [self getLineRangeFromTokenizer:tokenizer atIndex:11];
2722 XCTAssertEqual(range.range.location, 0u);
2723 XCTAssertEqual(range.range.length, 11u);
2724
2725 range = [self getLineRangeFromTokenizer:tokenizer atIndex:12];
2726 XCTAssertEqual(range.range.location, 12u);
2727 XCTAssertEqual(range.range.length, 20u);
2728
2729 range = [self getLineRangeFromTokenizer:tokenizer atIndex:15];
2730 XCTAssertEqual(range.range.location, 12u);
2731 XCTAssertEqual(range.range.length, 20u);
2732
2733 range = [self getLineRangeFromTokenizer:tokenizer atIndex:32];
2734 XCTAssertEqual(range.range.location, 12u);
2735 XCTAssertEqual(range.range.length, 20u);
2736}
id< UITextInputTokenizer > tokenizer()

◆ testFlutterTokenizerLineEnclosingEndOfDocumentInBackwardDirectionShouldNotReturnNil

- (void) testFlutterTokenizerLineEnclosingEndOfDocumentInBackwardDirectionShouldNotReturnNil
implementation

Definition at line 92 of file FlutterTextInputPluginTest.mm.

2738 {
2739 FlutterTextInputView* inputView = [[FlutterTextInputView alloc] initWithOwner:textInputPlugin];
2740 [inputView insertText:@"0123456789\n012345"];
2741 id<UITextInputTokenizer> tokenizer = [inputView tokenizer];
2742
2743 FlutterTextRange* range =
2744 (FlutterTextRange*)[tokenizer rangeEnclosingPosition:[inputView endOfDocument]
2745 withGranularity:UITextGranularityLine
2746 inDirection:UITextStorageDirectionBackward];
2747 XCTAssertEqual(range.range.location, 11u);
2748 XCTAssertEqual(range.range.length, 6u);
2749}

◆ testFlutterTokenizerLineEnclosingEndOfDocumentInForwardDirectionShouldReturnNilOnIOS17

- (void) testFlutterTokenizerLineEnclosingEndOfDocumentInForwardDirectionShouldReturnNilOnIOS17
implementation

Definition at line 92 of file FlutterTextInputPluginTest.mm.

2751 {
2752 FlutterTextInputView* inputView = [[FlutterTextInputView alloc] initWithOwner:textInputPlugin];
2753 [inputView insertText:@"0123456789\n012345"];
2754 id<UITextInputTokenizer> tokenizer = [inputView tokenizer];
2755
2756 FlutterTextRange* range =
2757 (FlutterTextRange*)[tokenizer rangeEnclosingPosition:[inputView endOfDocument]
2758 withGranularity:UITextGranularityLine
2759 inDirection:UITextStorageDirectionForward];
2760 if (@available(iOS 17.0, *)) {
2761 XCTAssertNil(range);
2762 } else {
2763 XCTAssertEqual(range.range.location, 11u);
2764 XCTAssertEqual(range.range.length, 6u);
2765 }
2766}

◆ testFlutterTokenizerLineEnclosingOutOfRangePositionShouldReturnNilOnIOS17

- (void) testFlutterTokenizerLineEnclosingOutOfRangePositionShouldReturnNilOnIOS17
implementation

Definition at line 92 of file FlutterTextInputPluginTest.mm.

2768 {
2769 FlutterTextInputView* inputView = [[FlutterTextInputView alloc] initWithOwner:textInputPlugin];
2770 [inputView insertText:@"0123456789\n012345"];
2771 id<UITextInputTokenizer> tokenizer = [inputView tokenizer];
2772
2774 FlutterTextRange* range =
2775 (FlutterTextRange*)[tokenizer rangeEnclosingPosition:position
2776 withGranularity:UITextGranularityLine
2777 inDirection:UITextStorageDirectionForward];
2778 if (@available(iOS 17.0, *)) {
2779 XCTAssertNil(range);
2780 } else {
2781 XCTAssertEqual(range.range.location, 0u);
2782 XCTAssertEqual(range.range.length, 0u);
2783 }
2784}

◆ testGarbageInputViewsAreNotRemovedImmediately

- (void) testGarbageInputViewsAreNotRemovedImmediately
implementation

Definition at line 92 of file FlutterTextInputPluginTest.mm.

2586 {
2587 // Add a password field that should autofill.
2588 [self setClientId:123 configuration:self.mutablePasswordTemplateCopy];
2589 [self ensureOnlyActiveViewCanBecomeFirstResponder];
2590
2591 XCTAssertEqual(self.installedInputViews.count, 1ul);
2592 // Add an input field that doesn't autofill. This should remove the password
2593 // field, but not immediately.
2594 [self setClientId:124 configuration:self.mutableTemplateCopy];
2595 [self ensureOnlyActiveViewCanBecomeFirstResponder];
2596
2597 XCTAssertEqual(self.installedInputViews.count, 2ul);
2598
2599 [self commitAutofillContextAndVerify];
2600}

◆ testIgnoresSelectionChangeIfSelectionIsDisabled

- (void) testIgnoresSelectionChangeIfSelectionIsDisabled
implementation

Definition at line 92 of file FlutterTextInputPluginTest.mm.

356 {
357 FlutterTextInputView* inputView = [[FlutterTextInputView alloc] initWithOwner:textInputPlugin];
358 __block int updateCount = 0;
359 OCMStub([engine flutterTextInputView:inputView updateEditingClient:0 withState:[OCMArg isNotNil]])
360 .andDo(^(NSInvocation* invocation) {
361 updateCount++;
362 });
363
364 [inputView.text setString:@"Some initial text"];
365 XCTAssertEqual(updateCount, 0);
366
367 FlutterTextRange* textRange = [FlutterTextRange rangeWithNSRange:NSMakeRange(0, 1)];
368 [inputView setSelectedTextRange:textRange];
369 XCTAssertEqual(updateCount, 1);
370
371 // Disable the interactive selection.
372 NSDictionary* config = self.mutableTemplateCopy;
373 [config setValue:@(NO) forKey:@"enableInteractiveSelection"];
374 [config setValue:@(NO) forKey:@"obscureText"];
375 [config setValue:@(NO) forKey:@"enableDeltaModel"];
376 [inputView configureWithDictionary:config];
377
378 textRange = [FlutterTextRange rangeWithNSRange:NSMakeRange(2, 3)];
379 [inputView setSelectedTextRange:textRange];
380 // The update count does not change.
381 XCTAssertEqual(updateCount, 1);
382}
void setSelectedTextRange:(UITextRange *selectedTextRange)

◆ testInitialActiveViewCantAccessTextInputDelegate

- (void) testInitialActiveViewCantAccessTextInputDelegate
implementation

Definition at line 92 of file FlutterTextInputPluginTest.mm.

2658 {
2659 // Before the framework sends the first text input configuration,
2660 // the dummy "activeView" we use should never have access to
2661 // its textInputDelegate.
2663}
id< FlutterTextInputDelegate > textInputDelegate()

◆ testInputActionContinueAction

- (void) testInputActionContinueAction
implementation

Definition at line 92 of file FlutterTextInputPluginTest.mm.

765 {
766 id mockBinaryMessenger = OCMClassMock([FlutterBinaryMessengerRelay class]);
767 FlutterEngine* testEngine = [[FlutterEngine alloc] init];
768 [testEngine setBinaryMessenger:mockBinaryMessenger];
769 [testEngine runWithEntrypoint:FlutterDefaultDartEntrypoint initialRoute:@"test"];
770
771 FlutterTextInputPlugin* inputPlugin =
772 [[FlutterTextInputPlugin alloc] initWithDelegate:(id<FlutterTextInputDelegate>)testEngine];
773 FlutterTextInputView* inputView = [[FlutterTextInputView alloc] initWithOwner:inputPlugin];
774
775 [testEngine flutterTextInputView:inputView
776 performAction:FlutterTextInputActionContinue
777 withClient:123];
778
779 FlutterMethodCall* methodCall =
780 [FlutterMethodCall methodCallWithMethodName:@"TextInputClient.performAction"
781 arguments:@[ @(123), @"TextInputAction.continueAction" ]];
782 NSData* encodedMethodCall = [[FlutterJSONMethodCodec sharedInstance] encodeMethodCall:methodCall];
783 OCMVerify([mockBinaryMessenger sendOnChannel:@"flutter/textinput" message:encodedMethodCall]);
784}
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)
instancetype sharedInstance()
Win32Message message

◆ testInputHiderOverlapWithTextWhenScribbleIsDisabledAfterIOS17AndDoesNotOverlapBeforeIOS17

- (void) testInputHiderOverlapWithTextWhenScribbleIsDisabledAfterIOS17AndDoesNotOverlapBeforeIOS17
implementation

Definition at line 92 of file FlutterTextInputPluginTest.mm.

440 {
441 FlutterTextInputPlugin* myInputPlugin =
442 [[FlutterTextInputPlugin alloc] initWithDelegate:OCMClassMock([FlutterEngine class])];
443
444 FlutterMethodCall* setClientCall =
445 [FlutterMethodCall methodCallWithMethodName:@"TextInput.setClient"
446 arguments:@[ @(123), self.mutableTemplateCopy ]];
447 [myInputPlugin handleMethodCall:setClientCall
448 result:^(id _Nullable result){
449 }];
450
451 FlutterTextInputView* mockInputView = OCMPartialMock(myInputPlugin.activeView);
452 OCMStub([mockInputView isScribbleAvailable]).andReturn(NO);
453
454 // yOffset = 200.
455 NSArray* yOffsetMatrix = @[ @1, @0, @0, @0, @0, @1, @0, @0, @0, @0, @1, @0, @0, @200, @0, @1 ];
456
457 FlutterMethodCall* setPlatformViewClientCall =
458 [FlutterMethodCall methodCallWithMethodName:@"TextInput.setEditableSizeAndTransform"
459 arguments:@{@"transform" : yOffsetMatrix}];
460 [myInputPlugin handleMethodCall:setPlatformViewClientCall
461 result:^(id _Nullable result){
462 }];
463
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");
467
468 } else {
469 XCTAssert(CGRectEqualToRect(myInputPlugin.inputHider.frame, CGRectZero),
470 @"The input hider should be on the origin of screen on and before iOS 16.");
471 }
472}
FlutterTextInputViewAccessibilityHider * inputHider

◆ testInputViewCrash

- (void) testInputViewCrash
implementation

Definition at line 92 of file FlutterTextInputPluginTest.mm.

650 {
651 FlutterTextInputView* activeView = nil;
652 @autoreleasepool {
653 FlutterEngine* flutterEngine = [[FlutterEngine alloc] init];
654 FlutterTextInputPlugin* inputPlugin = [[FlutterTextInputPlugin alloc]
655 initWithDelegate:(id<FlutterTextInputDelegate>)flutterEngine];
656 activeView = inputPlugin.activeView;
657 }
658 [activeView updateEditingState];
659}

◆ testInputViewsDoNotHaveUITextInteractions

- (void) testInputViewsDoNotHaveUITextInteractions
implementation

Definition at line 92 of file FlutterTextInputPluginTest.mm.

1487 {
1488 if (@available(iOS 13.0, *)) {
1489 FlutterTextInputView* inputView = [[FlutterTextInputView alloc] initWithOwner:textInputPlugin];
1490 BOOL hasTextInteraction = NO;
1491 for (id interaction in inputView.interactions) {
1492 hasTextInteraction = [interaction isKindOfClass:[UITextInteraction class]];
1493 if (hasTextInteraction) {
1494 break;
1495 }
1496 }
1497 XCTAssertFalse(hasTextInteraction);
1498 }
1499}

◆ testInputViewsHasNonNilInputDelegate

- (void) testInputViewsHasNonNilInputDelegate
implementation

Definition at line 92 of file FlutterTextInputPluginTest.mm.

1465 {
1466 if (@available(iOS 13.0, *)) {
1467 FlutterTextInputView* inputView = [[FlutterTextInputView alloc] initWithOwner:textInputPlugin];
1468 [UIApplication.sharedApplication.keyWindow addSubview:inputView];
1469
1470 [inputView setTextInputClient:123];
1471 [inputView reloadInputViews];
1472 [inputView becomeFirstResponder];
1473 NSAssert(inputView.isFirstResponder, @"inputView is not first responder");
1474 inputView.inputDelegate = nil;
1475
1476 FlutterTextInputView* mockInputView = OCMPartialMock(inputView);
1477 [mockInputView setTextInputState:@{
1478 @"text" : @"COMPOSING",
1479 @"composingBase" : @1,
1480 @"composingExtent" : @3
1481 }];
1482 OCMVerify([mockInputView setInputDelegate:[OCMArg isNotNil]]);
1483 [inputView removeFromSuperview];
1484 }
1485}
void setTextInputClient:(int client)

◆ testInsertTextAddsPlaceholderSelectionRects

- (void) testInsertTextAddsPlaceholderSelectionRects
implementation

Definition at line 92 of file FlutterTextInputPluginTest.mm.

2212 {
2213 FlutterTextInputView* inputView = [[FlutterTextInputView alloc] initWithOwner:textInputPlugin];
2214 [inputView
2215 setTextInputState:@{@"text" : @"test", @"selectionBase" : @1, @"selectionExtent" : @1}];
2216
2218 [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(0, 0, 100, 100) position:0U];
2219 FlutterTextSelectionRect* second =
2220 [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(100, 100, 100, 100) position:1U];
2222 [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(200, 200, 100, 100) position:2U];
2223 FlutterTextSelectionRect* fourth =
2224 [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(300, 300, 100, 100) position:3U];
2225 [inputView setSelectionRects:@[ first, second, third, fourth ]];
2226
2227 // Inserts additional selection rects at the selection start
2228 [inputView insertText:@"in"];
2229 NSArray* selectionRects =
2230 [inputView selectionRectsForRange:[FlutterTextRange rangeWithNSRange:NSMakeRange(0, 6)]];
2231 XCTAssertEqual(6U, [selectionRects count]);
2232
2233 XCTAssertEqual(first.position, ((FlutterTextSelectionRect*)selectionRects[0]).position);
2234 XCTAssertTrue(CGRectEqualToRect(first.rect, ((FlutterTextSelectionRect*)selectionRects[0]).rect));
2235
2236 XCTAssertEqual(second.position, ((FlutterTextSelectionRect*)selectionRects[1]).position);
2237 XCTAssertTrue(
2238 CGRectEqualToRect(second.rect, ((FlutterTextSelectionRect*)selectionRects[1]).rect));
2239
2240 XCTAssertEqual(second.position + 1, ((FlutterTextSelectionRect*)selectionRects[2]).position);
2241 XCTAssertTrue(
2242 CGRectEqualToRect(second.rect, ((FlutterTextSelectionRect*)selectionRects[2]).rect));
2243
2244 XCTAssertEqual(second.position + 2, ((FlutterTextSelectionRect*)selectionRects[3]).position);
2245 XCTAssertTrue(
2246 CGRectEqualToRect(second.rect, ((FlutterTextSelectionRect*)selectionRects[3]).rect));
2247
2248 XCTAssertEqual(third.position + 2, ((FlutterTextSelectionRect*)selectionRects[4]).position);
2249 XCTAssertTrue(CGRectEqualToRect(third.rect, ((FlutterTextSelectionRect*)selectionRects[4]).rect));
2250
2251 XCTAssertEqual(fourth.position + 2, ((FlutterTextSelectionRect*)selectionRects[5]).position);
2252 XCTAssertTrue(
2253 CGRectEqualToRect(fourth.rect, ((FlutterTextSelectionRect*)selectionRects[5]).rect));
2254}
int count
Definition: FontMgrTest.cpp:50
NSArray * selectionRectsForRange:(UITextRange *range)
NSArray< FlutterTextSelectionRect * > * selectionRects

◆ testInteractiveKeyboardAfterUserScrollToTopOfKeyboardWillTakeScreenshot

- (void) testInteractiveKeyboardAfterUserScrollToTopOfKeyboardWillTakeScreenshot
implementation

Definition at line 92 of file FlutterTextInputPluginTest.mm.

2985 {
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];
2994
2995 [viewController loadView];
2996
2997 FlutterTextInputView* inputView = [[FlutterTextInputView alloc] initWithOwner:textInputPlugin];
2998 [UIApplication.sharedApplication.keyWindow addSubview:inputView];
2999
3000 [inputView setTextInputClient:123];
3001 [inputView reloadInputViews];
3002 [inputView becomeFirstResponder];
3003
3004 if (textInputPlugin.keyboardView.superview != nil) {
3005 for (UIView* subView in textInputPlugin.keyboardViewContainer.subviews) {
3006 [subView removeFromSuperview];
3007 }
3008 }
3009 XCTAssert(textInputPlugin.keyboardView.superview == nil);
3010 CGRect keyboardFrame = CGRectMake(0, 500, 500, 500);
3011 [NSNotificationCenter.defaultCenter
3012 postNotificationName:UIKeyboardWillShowNotification
3013 object:nil
3014 userInfo:@{UIKeyboardFrameEndUserInfoKey : @(keyboardFrame)}];
3015 FlutterMethodCall* onPointerMoveCall =
3016 [FlutterMethodCall methodCallWithMethodName:@"TextInput.onPointerMoveForInteractiveKeyboard"
3017 arguments:@{@"pointerY" : @(510)}];
3018 [textInputPlugin handleMethodCall:onPointerMoveCall
3019 result:^(id _Nullable result){
3020 }];
3021 XCTAssertFalse(textInputPlugin.keyboardView.superview == nil);
3022 for (UIView* subView in textInputPlugin.keyboardViewContainer.subviews) {
3023 [subView removeFromSuperview];
3024 }
3026}
GLFWwindow * window
Definition: main.cc:45

◆ testInteractiveKeyboardAfterUserScrollWillResignFirstResponder

- (void) testInteractiveKeyboardAfterUserScrollWillResignFirstResponder
implementation

Definition at line 92 of file FlutterTextInputPluginTest.mm.

2961 {
2962 FlutterTextInputView* inputView = [[FlutterTextInputView alloc] initWithOwner:textInputPlugin];
2963 [UIApplication.sharedApplication.keyWindow addSubview:inputView];
2964
2965 [inputView setTextInputClient:123];
2966 [inputView reloadInputViews];
2967 [inputView becomeFirstResponder];
2968 XCTAssert(inputView.isFirstResponder);
2969
2970 CGRect keyboardFrame = CGRectMake(0, 500, 500, 500);
2971 [NSNotificationCenter.defaultCenter
2972 postNotificationName:UIKeyboardWillShowNotification
2973 object:nil
2974 userInfo:@{UIKeyboardFrameEndUserInfoKey : @(keyboardFrame)}];
2975 FlutterMethodCall* onPointerMoveCall =
2976 [FlutterMethodCall methodCallWithMethodName:@"TextInput.onPointerMoveForInteractiveKeyboard"
2977 arguments:@{@"pointerY" : @(500)}];
2978 [textInputPlugin handleMethodCall:onPointerMoveCall
2979 result:^(id _Nullable result){
2980 }];
2981 XCTAssertFalse(inputView.isFirstResponder);
2983}

◆ testInteractiveKeyboardDidResignFirstResponderDelegateisCalledAfterDismissedKeyboard

- (void) testInteractiveKeyboardDidResignFirstResponderDelegateisCalledAfterDismissedKeyboard
implementation

Definition at line 92 of file FlutterTextInputPluginTest.mm.

3183 {
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];
3192
3193 [viewController loadView];
3194
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];
3201 });
3202 CGRect keyboardFrame = CGRectMake(0, 500, 500, 500);
3203 [NSNotificationCenter.defaultCenter
3204 postNotificationName:UIKeyboardWillShowNotification
3205 object:nil
3206 userInfo:@{UIKeyboardFrameEndUserInfoKey : @(keyboardFrame)}];
3207 FlutterMethodCall* initialMoveCall =
3208 [FlutterMethodCall methodCallWithMethodName:@"TextInput.onPointerMoveForInteractiveKeyboard"
3209 arguments:@{@"pointerY" : @(500)}];
3210 [textInputPlugin handleMethodCall:initialMoveCall
3211 result:^(id _Nullable result){
3212 }];
3213 FlutterMethodCall* subsequentMoveCall =
3214 [FlutterMethodCall methodCallWithMethodName:@"TextInput.onPointerMoveForInteractiveKeyboard"
3215 arguments:@{@"pointerY" : @(1000)}];
3216 [textInputPlugin handleMethodCall:subsequentMoveCall
3217 result:^(id _Nullable result){
3218 }];
3219
3220 FlutterMethodCall* pointerUpCall =
3221 [FlutterMethodCall methodCallWithMethodName:@"TextInput.onPointerUpForInteractiveKeyboard"
3222 arguments:@{@"pointerY" : @(1000)}];
3223 [textInputPlugin handleMethodCall:pointerUpCall
3224 result:^(id _Nullable result){
3225 }];
3226
3227 [self waitForExpectations:@[ expectation ] timeout:2.0];
3229}

◆ testInteractiveKeyboardFindFirstResponderIsNilRecursive

- (void) testInteractiveKeyboardFindFirstResponderIsNilRecursive
implementation

Definition at line 92 of file FlutterTextInputPluginTest.mm.

3172 {
3173 FlutterTextInputView* inputView = [[FlutterTextInputView alloc] initWithOwner:textInputPlugin];
3174 [UIApplication.sharedApplication.keyWindow addSubview:inputView];
3175 [inputView setTextInputClient:123];
3176 [inputView reloadInputViews];
3177
3178 UIView* firstResponder = UIApplication.sharedApplication.keyWindow.flutterFirstResponder;
3179 XCTAssertNil(firstResponder);
3181}

◆ testInteractiveKeyboardFindFirstResponderRecursive

- (void) testInteractiveKeyboardFindFirstResponderRecursive
implementation

Definition at line 92 of file FlutterTextInputPluginTest.mm.

3134 {
3135 FlutterTextInputView* inputView = [[FlutterTextInputView alloc] initWithOwner:textInputPlugin];
3136 [UIApplication.sharedApplication.keyWindow addSubview:inputView];
3137 [inputView setTextInputClient:123];
3138 [inputView reloadInputViews];
3139 [inputView becomeFirstResponder];
3140
3141 UIView* firstResponder = UIApplication.sharedApplication.keyWindow.flutterFirstResponder;
3142 XCTAssertEqualObjects(inputView, firstResponder);
3144}

◆ testInteractiveKeyboardFindFirstResponderRecursiveInMultipleSubviews

- (void) testInteractiveKeyboardFindFirstResponderRecursiveInMultipleSubviews
implementation

Definition at line 92 of file FlutterTextInputPluginTest.mm.

3146 {
3147 FlutterTextInputView* inputView = [[FlutterTextInputView alloc] initWithOwner:textInputPlugin];
3148 FlutterTextInputView* subInputView = [[FlutterTextInputView alloc] initWithOwner:textInputPlugin];
3149 FlutterTextInputView* otherSubInputView =
3150 [[FlutterTextInputView alloc] initWithOwner:textInputPlugin];
3151 FlutterTextInputView* subFirstResponderInputView =
3152 [[FlutterTextInputView alloc] initWithOwner:textInputPlugin];
3153 [subInputView addSubview:subFirstResponderInputView];
3154 [inputView addSubview:subInputView];
3155 [inputView addSubview:otherSubInputView];
3156 [UIApplication.sharedApplication.keyWindow addSubview:inputView];
3157 [inputView setTextInputClient:123];
3158 [inputView reloadInputViews];
3159 [subInputView setTextInputClient:123];
3160 [subInputView reloadInputViews];
3161 [otherSubInputView setTextInputClient:123];
3162 [otherSubInputView reloadInputViews];
3163 [subFirstResponderInputView setTextInputClient:123];
3164 [subFirstResponderInputView reloadInputViews];
3165 [subFirstResponderInputView becomeFirstResponder];
3166
3167 UIView* firstResponder = UIApplication.sharedApplication.keyWindow.flutterFirstResponder;
3168 XCTAssertEqualObjects(subFirstResponderInputView, firstResponder);
3170}

◆ testInteractiveKeyboardKeyboardAnimatesToDismissalPositionalOnPointerUp

- (void) testInteractiveKeyboardKeyboardAnimatesToDismissalPositionalOnPointerUp
implementation

Definition at line 92 of file FlutterTextInputPluginTest.mm.

3394 {
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];
3403
3404 [viewController loadView];
3405
3406 XCTestExpectation* expectation =
3407 [[XCTestExpectation alloc] initWithDescription:@"Keyboard animates to proper position."];
3408 CGRect keyboardFrame = CGRectMake(0, 500, 500, 500);
3409 [NSNotificationCenter.defaultCenter
3410 postNotificationName:UIKeyboardWillShowNotification
3411 object:nil
3412 userInfo:@{UIKeyboardFrameEndUserInfoKey : @(keyboardFrame)}];
3413 FlutterMethodCall* initialMoveCall =
3414 [FlutterMethodCall methodCallWithMethodName:@"TextInput.onPointerMoveForInteractiveKeyboard"
3415 arguments:@{@"pointerY" : @(500)}];
3416 [textInputPlugin handleMethodCall:initialMoveCall
3417 result:^(id _Nullable result){
3418 }];
3419 FlutterMethodCall* subsequentMoveCall =
3420 [FlutterMethodCall methodCallWithMethodName:@"TextInput.onPointerMoveForInteractiveKeyboard"
3421 arguments:@{@"pointerY" : @(1000)}];
3422 [textInputPlugin handleMethodCall:subsequentMoveCall
3423 result:^(id _Nullable result){
3424 }];
3425
3426 FlutterMethodCall* pointerUpCall =
3427 [FlutterMethodCall methodCallWithMethodName:@"TextInput.onPointerUpForInteractiveKeyboard"
3428 arguments:@{@"pointerY" : @(1000)}];
3429 [textInputPlugin
3430 handleMethodCall:pointerUpCall
3431 result:^(id _Nullable result) {
3432 XCTAssertEqual(textInputPlugin.keyboardViewContainer.frame.origin.y,
3433 viewController.flutterScreenIfViewLoaded.bounds.size.height);
3434 [expectation fulfill];
3435 }];
3437}

◆ testInteractiveKeyboardKeyboardAnimatesToOriginalPositionalOnPointerUp

- (void) testInteractiveKeyboardKeyboardAnimatesToOriginalPositionalOnPointerUp
implementation

Definition at line 92 of file FlutterTextInputPluginTest.mm.

3342 {
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];
3351
3352 [viewController loadView];
3353
3354 XCTestExpectation* expectation =
3355 [[XCTestExpectation alloc] initWithDescription:@"Keyboard animates to proper position."];
3356 CGRect keyboardFrame = CGRectMake(0, 500, 500, 500);
3357 [NSNotificationCenter.defaultCenter
3358 postNotificationName:UIKeyboardWillShowNotification
3359 object:nil
3360 userInfo:@{UIKeyboardFrameEndUserInfoKey : @(keyboardFrame)}];
3361 FlutterMethodCall* initialMoveCall =
3362 [FlutterMethodCall methodCallWithMethodName:@"TextInput.onPointerMoveForInteractiveKeyboard"
3363 arguments:@{@"pointerY" : @(500)}];
3364 [textInputPlugin handleMethodCall:initialMoveCall
3365 result:^(id _Nullable result){
3366 }];
3367 FlutterMethodCall* subsequentMoveCall =
3368 [FlutterMethodCall methodCallWithMethodName:@"TextInput.onPointerMoveForInteractiveKeyboard"
3369 arguments:@{@"pointerY" : @(1000)}];
3370 [textInputPlugin handleMethodCall:subsequentMoveCall
3371 result:^(id _Nullable result){
3372 }];
3373 FlutterMethodCall* upwardVelocityMoveCall =
3374 [FlutterMethodCall methodCallWithMethodName:@"TextInput.onPointerMoveForInteractiveKeyboard"
3375 arguments:@{@"pointerY" : @(500)}];
3376 [textInputPlugin handleMethodCall:upwardVelocityMoveCall
3377 result:^(id _Nullable result){
3378 }];
3379
3380 FlutterMethodCall* pointerUpCall =
3381 [FlutterMethodCall methodCallWithMethodName:@"TextInput.onPointerUpForInteractiveKeyboard"
3382 arguments:@{@"pointerY" : @(0)}];
3383 [textInputPlugin
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];
3390 }];
3392}

◆ testInteractiveKeyboardKeyboardReappearsAfterPointerLiftedAboveMiddleYOfKeyboard

- (void) testInteractiveKeyboardKeyboardReappearsAfterPointerLiftedAboveMiddleYOfKeyboard
implementation

Definition at line 92 of file FlutterTextInputPluginTest.mm.

3283 {
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];
3292
3293 [viewController loadView];
3294
3295 FlutterTextInputView* inputView = [[FlutterTextInputView alloc] initWithOwner:textInputPlugin];
3296 [UIApplication.sharedApplication.keyWindow addSubview:inputView];
3297
3298 [inputView setTextInputClient:123];
3299 [inputView reloadInputViews];
3300 [inputView becomeFirstResponder];
3301
3302 CGRect keyboardFrame = CGRectMake(0, 500, 500, 500);
3303 [NSNotificationCenter.defaultCenter
3304 postNotificationName:UIKeyboardWillShowNotification
3305 object:nil
3306 userInfo:@{UIKeyboardFrameEndUserInfoKey : @(keyboardFrame)}];
3307 FlutterMethodCall* initialMoveCall =
3308 [FlutterMethodCall methodCallWithMethodName:@"TextInput.onPointerMoveForInteractiveKeyboard"
3309 arguments:@{@"pointerY" : @(500)}];
3310 [textInputPlugin handleMethodCall:initialMoveCall
3311 result:^(id _Nullable result){
3312 }];
3313 FlutterMethodCall* subsequentMoveCall =
3314 [FlutterMethodCall methodCallWithMethodName:@"TextInput.onPointerMoveForInteractiveKeyboard"
3315 arguments:@{@"pointerY" : @(1000)}];
3316 [textInputPlugin handleMethodCall:subsequentMoveCall
3317 result:^(id _Nullable result){
3318 }];
3319
3320 FlutterMethodCall* subsequentMoveBackUpCall =
3321 [FlutterMethodCall methodCallWithMethodName:@"TextInput.onPointerMoveForInteractiveKeyboard"
3322 arguments:@{@"pointerY" : @(0)}];
3323 [textInputPlugin handleMethodCall:subsequentMoveBackUpCall
3324 result:^(id _Nullable result){
3325 }];
3326
3327 FlutterMethodCall* pointerUpCall =
3328 [FlutterMethodCall methodCallWithMethodName:@"TextInput.onPointerUpForInteractiveKeyboard"
3329 arguments:@{@"pointerY" : @(0)}];
3330 [textInputPlugin handleMethodCall:pointerUpCall
3331 result:^(id _Nullable result){
3332 }];
3333 NSPredicate* predicate = [NSPredicate predicateWithBlock:^BOOL(id item, NSDictionary* bindings) {
3334 return textInputPlugin.cachedFirstResponder.isFirstResponder;
3335 }];
3336 XCTNSPredicateExpectation* expectation =
3337 [[XCTNSPredicateExpectation alloc] initWithPredicate:predicate object:nil];
3338 [self waitForExpectations:@[ expectation ] timeout:10.0];
3340}

◆ testInteractiveKeyboardScreenshotDismissedAfterPointerLiftedAboveMiddleYOfKeyboard

- (void) testInteractiveKeyboardScreenshotDismissedAfterPointerLiftedAboveMiddleYOfKeyboard
implementation

Definition at line 92 of file FlutterTextInputPluginTest.mm.

3231 {
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];
3240
3241 [viewController loadView];
3242
3243 CGRect keyboardFrame = CGRectMake(0, 500, 500, 500);
3244 [NSNotificationCenter.defaultCenter
3245 postNotificationName:UIKeyboardWillShowNotification
3246 object:nil
3247 userInfo:@{UIKeyboardFrameEndUserInfoKey : @(keyboardFrame)}];
3248 FlutterMethodCall* initialMoveCall =
3249 [FlutterMethodCall methodCallWithMethodName:@"TextInput.onPointerMoveForInteractiveKeyboard"
3250 arguments:@{@"pointerY" : @(500)}];
3251 [textInputPlugin handleMethodCall:initialMoveCall
3252 result:^(id _Nullable result){
3253 }];
3254 FlutterMethodCall* subsequentMoveCall =
3255 [FlutterMethodCall methodCallWithMethodName:@"TextInput.onPointerMoveForInteractiveKeyboard"
3256 arguments:@{@"pointerY" : @(1000)}];
3257 [textInputPlugin handleMethodCall:subsequentMoveCall
3258 result:^(id _Nullable result){
3259 }];
3260
3261 FlutterMethodCall* subsequentMoveBackUpCall =
3262 [FlutterMethodCall methodCallWithMethodName:@"TextInput.onPointerMoveForInteractiveKeyboard"
3263 arguments:@{@"pointerY" : @(0)}];
3264 [textInputPlugin handleMethodCall:subsequentMoveBackUpCall
3265 result:^(id _Nullable result){
3266 }];
3267
3268 FlutterMethodCall* pointerUpCall =
3269 [FlutterMethodCall methodCallWithMethodName:@"TextInput.onPointerUpForInteractiveKeyboard"
3270 arguments:@{@"pointerY" : @(0)}];
3271 [textInputPlugin handleMethodCall:pointerUpCall
3272 result:^(id _Nullable result){
3273 }];
3274 NSPredicate* predicate = [NSPredicate predicateWithBlock:^BOOL(id item, NSDictionary* bindings) {
3275 return textInputPlugin.keyboardViewContainer.subviews.count == 0;
3276 }];
3277 XCTNSPredicateExpectation* expectation =
3278 [[XCTNSPredicateExpectation alloc] initWithPredicate:predicate object:nil];
3279 [self waitForExpectations:@[ expectation ] timeout:10.0];
3281}

◆ testInteractiveKeyboardScreenshotWillBeMovedDownAfterUserScroll

- (void) testInteractiveKeyboardScreenshotWillBeMovedDownAfterUserScroll
implementation

Definition at line 92 of file FlutterTextInputPluginTest.mm.

3028 {
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];
3037
3038 [viewController loadView];
3039
3040 FlutterTextInputView* inputView = [[FlutterTextInputView alloc] initWithOwner:textInputPlugin];
3041 [UIApplication.sharedApplication.keyWindow addSubview:inputView];
3042
3043 [inputView setTextInputClient:123];
3044 [inputView reloadInputViews];
3045 [inputView becomeFirstResponder];
3046
3047 CGRect keyboardFrame = CGRectMake(0, 500, 500, 500);
3048 [NSNotificationCenter.defaultCenter
3049 postNotificationName:UIKeyboardWillShowNotification
3050 object:nil
3051 userInfo:@{UIKeyboardFrameEndUserInfoKey : @(keyboardFrame)}];
3052 FlutterMethodCall* onPointerMoveCall =
3053 [FlutterMethodCall methodCallWithMethodName:@"TextInput.onPointerMoveForInteractiveKeyboard"
3054 arguments:@{@"pointerY" : @(510)}];
3055 [textInputPlugin handleMethodCall:onPointerMoveCall
3056 result:^(id _Nullable result){
3057 }];
3058 XCTAssert(textInputPlugin.keyboardView.superview != nil);
3059
3060 XCTAssertEqual(textInputPlugin.keyboardViewContainer.frame.origin.y, keyboardFrame.origin.y);
3061
3062 FlutterMethodCall* onPointerMoveCallMove =
3063 [FlutterMethodCall methodCallWithMethodName:@"TextInput.onPointerMoveForInteractiveKeyboard"
3064 arguments:@{@"pointerY" : @(600)}];
3065 [textInputPlugin handleMethodCall:onPointerMoveCallMove
3066 result:^(id _Nullable result){
3067 }];
3068 XCTAssert(textInputPlugin.keyboardView.superview != nil);
3069
3070 XCTAssertEqual(textInputPlugin.keyboardViewContainer.frame.origin.y, 600.0);
3071
3072 for (UIView* subView in textInputPlugin.keyboardViewContainer.subviews) {
3073 [subView removeFromSuperview];
3074 }
3076}

◆ testInteractiveKeyboardScreenshotWillBeMovedToOrginalPositionAfterUserScroll

- (void) testInteractiveKeyboardScreenshotWillBeMovedToOrginalPositionAfterUserScroll
implementation

Definition at line 92 of file FlutterTextInputPluginTest.mm.

3078 {
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];
3087
3088 [viewController loadView];
3089
3090 FlutterTextInputView* inputView = [[FlutterTextInputView alloc] initWithOwner:textInputPlugin];
3091 [UIApplication.sharedApplication.keyWindow addSubview:inputView];
3092
3093 [inputView setTextInputClient:123];
3094 [inputView reloadInputViews];
3095 [inputView becomeFirstResponder];
3096
3097 CGRect keyboardFrame = CGRectMake(0, 500, 500, 500);
3098 [NSNotificationCenter.defaultCenter
3099 postNotificationName:UIKeyboardWillShowNotification
3100 object:nil
3101 userInfo:@{UIKeyboardFrameEndUserInfoKey : @(keyboardFrame)}];
3102 FlutterMethodCall* onPointerMoveCall =
3103 [FlutterMethodCall methodCallWithMethodName:@"TextInput.onPointerMoveForInteractiveKeyboard"
3104 arguments:@{@"pointerY" : @(500)}];
3105 [textInputPlugin handleMethodCall:onPointerMoveCall
3106 result:^(id _Nullable result){
3107 }];
3108 XCTAssert(textInputPlugin.keyboardView.superview != nil);
3109 XCTAssertEqual(textInputPlugin.keyboardViewContainer.frame.origin.y, keyboardFrame.origin.y);
3110
3111 FlutterMethodCall* onPointerMoveCallMove =
3112 [FlutterMethodCall methodCallWithMethodName:@"TextInput.onPointerMoveForInteractiveKeyboard"
3113 arguments:@{@"pointerY" : @(600)}];
3114 [textInputPlugin handleMethodCall:onPointerMoveCallMove
3115 result:^(id _Nullable result){
3116 }];
3117 XCTAssert(textInputPlugin.keyboardView.superview != nil);
3118 XCTAssertEqual(textInputPlugin.keyboardViewContainer.frame.origin.y, 600.0);
3119
3120 FlutterMethodCall* onPointerMoveCallBackUp =
3121 [FlutterMethodCall methodCallWithMethodName:@"TextInput.onPointerMoveForInteractiveKeyboard"
3122 arguments:@{@"pointerY" : @(10)}];
3123 [textInputPlugin handleMethodCall:onPointerMoveCallBackUp
3124 result:^(id _Nullable result){
3125 }];
3126 XCTAssert(textInputPlugin.keyboardView.superview != nil);
3127 XCTAssertEqual(textInputPlugin.keyboardViewContainer.frame.origin.y, keyboardFrame.origin.y);
3128 for (UIView* subView in textInputPlugin.keyboardViewContainer.subviews) {
3129 [subView removeFromSuperview];
3130 }
3132}

◆ testInteractiveKeyboardShowKeyboardAndRemoveScreenshotAnimationIsNotImmediatelyEnable

- (void) testInteractiveKeyboardShowKeyboardAndRemoveScreenshotAnimationIsNotImmediatelyEnable
implementation

Definition at line 92 of file FlutterTextInputPluginTest.mm.

3438 {
3439 [UIView setAnimationsEnabled:YES];
3440 [textInputPlugin showKeyboardAndRemoveScreenshot];
3441 XCTAssertFalse(
3442 UIView.areAnimationsEnabled,
3443 @"The animation should still be disabled following showKeyboardAndRemoveScreenshot");
3444}

◆ testInteractiveKeyboardShowKeyboardAndRemoveScreenshotAnimationIsReenabledAfterDelay

- (void) testInteractiveKeyboardShowKeyboardAndRemoveScreenshotAnimationIsReenabledAfterDelay
implementation

Definition at line 92 of file FlutterTextInputPluginTest.mm.

3446 {
3447 [UIView setAnimationsEnabled:YES];
3448 [textInputPlugin showKeyboardAndRemoveScreenshot];
3449
3450 NSPredicate* predicate = [NSPredicate predicateWithBlock:^BOOL(id item, NSDictionary* bindings) {
3451 // This will be enabled after a delay
3452 return UIView.areAnimationsEnabled;
3453 }];
3454 XCTNSPredicateExpectation* expectation =
3455 [[XCTNSPredicateExpectation alloc] initWithPredicate:predicate object:nil];
3456 [self waitForExpectations:@[ expectation ] timeout:10.0];
3457}

◆ testInvokeStartLiveTextInput

- (void) testInvokeStartLiveTextInput
implementation

Definition at line 92 of file FlutterTextInputPluginTest.mm.

219 {
220 FlutterMethodCall* methodCall =
221 [FlutterMethodCall methodCallWithMethodName:@"TextInput.startLiveTextInput" arguments:nil];
222 FlutterTextInputPlugin* mockPlugin = OCMPartialMock(textInputPlugin);
223 [mockPlugin handleMethodCall:methodCall
224 result:^(id _Nullable result){
225 }];
226 OCMVerify([mockPlugin startLiveTextInput]);
227}

◆ testKeyboardType

- (void) testKeyboardType
implementation

Definition at line 92 of file FlutterTextInputPluginTest.mm.

296 {
297 NSDictionary* config = self.mutableTemplateCopy;
298 [config setValue:@{@"name" : @"TextInputType.url"} forKey:@"inputType"];
299 [self setClientId:123 configuration:config];
300
301 // Find all the FlutterTextInputViews we created.
302 NSArray<FlutterTextInputView*>* inputFields = self.installedInputViews;
303
304 FlutterTextInputView* inputView = inputFields[0];
305
306 // Verify keyboardType is set to the value specified in config.
307 XCTAssertEqual(inputView.keyboardType, UIKeyboardTypeURL);
308}

◆ testNoDanglingEnginePointer

- (void) testNoDanglingEnginePointer
implementation

Definition at line 92 of file FlutterTextInputPluginTest.mm.

229 {
230 __weak FlutterTextInputPlugin* weakFlutterTextInputPlugin;
231 FlutterViewController* flutterViewController = [[FlutterViewController alloc] init];
232 __weak FlutterEngine* weakFlutterEngine;
233
234 FlutterTextInputView* currentView;
235
236 // The engine instance will be deallocated after the autorelease pool is drained.
237 @autoreleasepool {
238 FlutterEngine* flutterEngine = OCMClassMock([FlutterEngine class]);
239 weakFlutterEngine = flutterEngine;
240 NSAssert(weakFlutterEngine, @"flutter engine must not be nil");
241 FlutterTextInputPlugin* flutterTextInputPlugin = [[FlutterTextInputPlugin alloc]
242 initWithDelegate:(id<FlutterTextInputDelegate>)flutterEngine];
243 weakFlutterTextInputPlugin = flutterTextInputPlugin;
244 flutterTextInputPlugin.viewController = flutterViewController;
245
246 // Set client so the text input plugin has an active view.
247 NSDictionary* config = self.mutableTemplateCopy;
248 FlutterMethodCall* setClientCall =
249 [FlutterMethodCall methodCallWithMethodName:@"TextInput.setClient"
250 arguments:@[ [NSNumber numberWithInt:123], config ]];
251 [flutterTextInputPlugin handleMethodCall:setClientCall
252 result:^(id _Nullable result){
253 }];
254 currentView = flutterTextInputPlugin.activeView;
255 }
256
257 NSAssert(!weakFlutterEngine, @"flutter engine must be nil");
258 NSAssert(currentView, @"current view must not be nil");
259
260 XCTAssertNil(weakFlutterTextInputPlugin);
261 // Verify that the view can no longer access the deallocated engine/text input plugin
262 // instance.
263 XCTAssertNil(currentView.textInputDelegate);
264}

◆ testNoZombies

- (void) testNoZombies
implementation

Definition at line 92 of file FlutterTextInputPluginTest.mm.

638 {
639 // Regression test for https://github.com/flutter/flutter/issues/62501.
640 FlutterSecureTextInputView* passwordView =
641 [[FlutterSecureTextInputView alloc] initWithOwner:textInputPlugin];
642
643 @autoreleasepool {
644 // Initialize the lazy textField.
645 [passwordView.textField description];
646 }
647 XCTAssert([[passwordView.textField description] containsString:@"TextField"]);
648}

◆ testPasswordAutofillHack

- (void) testPasswordAutofillHack
implementation

Definition at line 92 of file FlutterTextInputPluginTest.mm.

2529 {
2530 NSDictionary* config = self.mutableTemplateCopy;
2531 [config setValue:@"YES" forKey:@"obscureText"];
2532 [self setClientId:123 configuration:config];
2533
2534 // Find all the FlutterTextInputViews we created.
2535 NSArray<FlutterTextInputView*>* inputFields = self.installedInputViews;
2536
2537 FlutterTextInputView* inputView = inputFields[0];
2538
2539 XCTAssert([inputView isKindOfClass:[UITextField class]]);
2540 // FlutterSecureTextInputView does not respond to font,
2541 // but it should return the default UITextField.font.
2542 XCTAssertNotEqual([inputView performSelector:@selector(font)], nil);
2543}
font
Font Metadata and Metrics.

◆ testPastingNonTextDisallowed

- (void) testPastingNonTextDisallowed
implementation

Definition at line 92 of file FlutterTextInputPluginTest.mm.

624 {
625 NSDictionary* config = self.mutableTemplateCopy;
626 [self setClientId:123 configuration:config];
627 NSArray<FlutterTextInputView*>* inputFields = self.installedInputViews;
628 FlutterTextInputView* inputView = inputFields[0];
629
630 UIPasteboard.generalPasteboard.color = UIColor.redColor;
631 XCTAssertNil(UIPasteboard.generalPasteboard.string);
632 XCTAssertFalse([inputView canPerformAction:@selector(paste:) withSender:nil]);
633 [inputView paste:nil];
634
635 XCTAssertEqualObjects(inputView.text, @"");
636}

◆ testPropagatePressEventsToViewController

- (void) testPropagatePressEventsToViewController
implementation

Definition at line 92 of file FlutterTextInputPluginTest.mm.

678 {
679 FlutterViewController* mockViewController = OCMPartialMock(viewController);
680 OCMStub([mockViewController pressesBegan:[OCMArg isNotNil] withEvent:[OCMArg isNotNil]]);
681 OCMStub([mockViewController pressesEnded:[OCMArg isNotNil] withEvent:[OCMArg isNotNil]]);
682
683 textInputPlugin.viewController = mockViewController;
684
685 NSDictionary* config = self.mutableTemplateCopy;
686 [self setClientId:123 configuration:config];
688 [self setTextInputShow];
689
690 [currentView pressesBegan:[NSSet setWithObjects:OCMClassMock([UIPress class]), nil]
691 withEvent:OCMClassMock([UIPressesEvent class])];
692
693 OCMVerify(times(1), [mockViewController pressesBegan:[OCMArg isNotNil]
694 withEvent:[OCMArg isNotNil]]);
695 OCMVerify(times(0), [mockViewController pressesEnded:[OCMArg isNotNil]
696 withEvent:[OCMArg isNotNil]]);
697
698 [currentView pressesEnded:[NSSet setWithObjects:OCMClassMock([UIPress class]), nil]
699 withEvent:OCMClassMock([UIPressesEvent class])];
700
701 OCMVerify(times(1), [mockViewController pressesBegan:[OCMArg isNotNil]
702 withEvent:[OCMArg isNotNil]]);
703 OCMVerify(times(1), [mockViewController pressesEnded:[OCMArg isNotNil]
704 withEvent:[OCMArg isNotNil]]);
705}
static SkISize times(const SkISize &size, float factor)
void pressesEnded:withEvent:(NSSet< UIPress * > *presses, [withEvent] ios(9.0) API_AVAILABLE)
void pressesBegan:withEvent:(NSSet< UIPress * > *presses, [withEvent] ios(9.0) API_AVAILABLE)

◆ testPropagatePressEventsToViewController2

- (void) testPropagatePressEventsToViewController2
implementation

Definition at line 92 of file FlutterTextInputPluginTest.mm.

707 {
708 FlutterViewController* mockViewController = OCMPartialMock(viewController);
709 OCMStub([mockViewController pressesBegan:[OCMArg isNotNil] withEvent:[OCMArg isNotNil]]);
710 OCMStub([mockViewController pressesEnded:[OCMArg isNotNil] withEvent:[OCMArg isNotNil]]);
711
712 textInputPlugin.viewController = mockViewController;
713
714 NSDictionary* config = self.mutableTemplateCopy;
715 [self setClientId:123 configuration:config];
716 [self setTextInputShow];
718
719 [currentView pressesBegan:[NSSet setWithObjects:OCMClassMock([UIPress class]), nil]
720 withEvent:OCMClassMock([UIPressesEvent class])];
721
722 OCMVerify(times(1), [mockViewController pressesBegan:[OCMArg isNotNil]
723 withEvent:[OCMArg isNotNil]]);
724 OCMVerify(times(0), [mockViewController pressesEnded:[OCMArg isNotNil]
725 withEvent:[OCMArg isNotNil]]);
726
727 // Switch focus to a different view.
728 [self setClientId:321 configuration:config];
729 [self setTextInputShow];
730 NSAssert(textInputPlugin.activeView, @"active view must not be nil");
731 NSAssert(textInputPlugin.activeView != currentView, @"active view must change");
732 currentView = textInputPlugin.activeView;
733 [currentView pressesEnded:[NSSet setWithObjects:OCMClassMock([UIPress class]), nil]
734 withEvent:OCMClassMock([UIPressesEvent class])];
735
736 OCMVerify(times(1), [mockViewController pressesBegan:[OCMArg isNotNil]
737 withEvent:[OCMArg isNotNil]]);
738 OCMVerify(times(1), [mockViewController pressesEnded:[OCMArg isNotNil]
739 withEvent:[OCMArg isNotNil]]);
740}

◆ testReplaceTestLocalAdjustSelectionAndMarkedTextRange

- (void) testReplaceTestLocalAdjustSelectionAndMarkedTextRange
implementation

Definition at line 92 of file FlutterTextInputPluginTest.mm.

803 {
804 FlutterTextInputView* inputView = [[FlutterTextInputView alloc] initWithOwner:textInputPlugin];
805 [inputView setMarkedText:@"test text" selectedRange:NSMakeRange(0, 5)];
806 NSRange selectedTextRange = ((FlutterTextRange*)inputView.selectedTextRange).range;
807 const NSRange markedTextRange = ((FlutterTextRange*)inputView.markedTextRange).range;
808 XCTAssertEqual(selectedTextRange.location, 0ul);
809 XCTAssertEqual(selectedTextRange.length, 5ul);
810 XCTAssertEqual(markedTextRange.location, 0ul);
811 XCTAssertEqual(markedTextRange.length, 9ul);
812
813 // Replaces space with space.
814 [inputView replaceRange:[FlutterTextRange rangeWithNSRange:NSMakeRange(4, 1)] withText:@" "];
816
817 XCTAssertEqual(selectedTextRange.location, 5ul);
818 XCTAssertEqual(selectedTextRange.length, 0ul);
819 XCTAssertEqual(inputView.markedTextRange, nil);
820}
void setMarkedText:selectedRange:(NSString *markedText, [selectedRange] NSRange markedSelectedRange)
UITextRange * markedTextRange
API_AVAILABLE(ios(13.0)) @interface FlutterTextPlaceholder UITextRange * selectedTextRange

◆ testScribbleSetSelectionRects

- (void) testScribbleSetSelectionRects
implementation

Definition at line 92 of file FlutterTextInputPluginTest.mm.

2602 {
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
2610 };
2611 [regularField setValue:@{
2612 @"uniqueIdentifier" : @"field1",
2613 @"hints" : @[ @"hint2" ],
2614 @"editingValue" : editingValue,
2615 }
2616 forKey:@"autofill"];
2617 [regularField addEntriesFromDictionary:editingValue];
2618 [self setClientId:123 configuration:regularField];
2619 XCTAssertEqual(self.installedInputViews.count, 1ul);
2620 XCTAssertEqual([textInputPlugin.activeView.selectionRects count], 0u);
2621
2622 NSArray<NSNumber*>* selectionRect = [NSArray arrayWithObjects:@0, @0, @100, @100, @0, @1, nil];
2623 NSArray* selectionRects = [NSArray arrayWithObjects:selectionRect, nil];
2624 FlutterMethodCall* methodCall =
2625 [FlutterMethodCall methodCallWithMethodName:@"Scribble.setSelectionRects"
2626 arguments:selectionRects];
2627 [textInputPlugin handleMethodCall:methodCall
2628 result:^(id _Nullable result){
2629 }];
2630
2631 XCTAssertEqual([textInputPlugin.activeView.selectionRects count], 1u);
2632}

◆ testSecureInput

- (void) testSecureInput
implementation

Definition at line 92 of file FlutterTextInputPluginTest.mm.

266 {
267 NSDictionary* config = self.mutableTemplateCopy;
268 [config setValue:@"YES" forKey:@"obscureText"];
269 [self setClientId:123 configuration:config];
270
271 // Find all the FlutterTextInputViews we created.
272 NSArray<FlutterTextInputView*>* inputFields = self.installedInputViews;
273
274 // There are no autofill and the mock framework requested a secure entry. The first and only
275 // inserted FlutterTextInputView should be a secure text entry one.
276 FlutterTextInputView* inputView = inputFields[0];
277
278 // Verify secureTextEntry is set to the correct value.
279 XCTAssertTrue(inputView.secureTextEntry);
280
281 // Verify keyboardType is set to the default value.
282 XCTAssertEqual(inputView.keyboardType, UIKeyboardTypeDefault);
283
284 // We should have only ever created one FlutterTextInputView.
285 XCTAssertEqual(inputFields.count, 1ul);
286
287 // The one FlutterTextInputView we inserted into the view hierarchy should be the text input
288 // plugin's active text input view.
289 XCTAssertEqual(inputView, textInputPlugin.textInputView);
290
291 // Despite not given an id in configuration, inputView has
292 // an autofill id.
293 XCTAssert(inputView.autofillId.length > 0);
294}

◆ testSelectionRectsForRange

- (void) testSelectionRectsForRange
implementation

Definition at line 92 of file FlutterTextInputPluginTest.mm.

1981 {
1982 FlutterTextInputView* inputView = [[FlutterTextInputView alloc] initWithOwner:textInputPlugin];
1983 [inputView setTextInputState:@{@"text" : @"COMPOSING"}];
1984
1985 CGRect testRect0 = CGRectMake(100, 100, 100, 100);
1986 CGRect testRect1 = CGRectMake(200, 200, 100, 100);
1987 [inputView setSelectionRects:@[
1988 [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(0, 0, 100, 100) position:0U],
1991 [FlutterTextSelectionRect selectionRectWithRect:CGRectMake(300, 300, 100, 100) position:3U],
1992 ]];
1993
1994 // Returns the matching rects within a range
1995 FlutterTextRange* range = [FlutterTextRange rangeWithNSRange:NSMakeRange(1, 2)];
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]);
1999
2000 // Returns a 0 width rect for a 0-length range
2001 range = [FlutterTextRange rangeWithNSRange:NSMakeRange(1, 0)];
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));
2006}
static void testRect1(skiatest::Reporter *reporter, const char *filename)
sk_sp< SkBlender > blender SkRect rect
Definition: SkRecords.h:350

◆ testSetMarkedTextDuringScribbleDoesNotTriggerUpdateEditingClient

- (void) testSetMarkedTextDuringScribbleDoesNotTriggerUpdateEditingClient
implementation

Definition at line 92 of file FlutterTextInputPluginTest.mm.

1315 {
1316 if (@available(iOS 14.0, *)) {
1317 FlutterTextInputView* inputView = [[FlutterTextInputView alloc] initWithOwner:textInputPlugin];
1318
1319 __block int updateCount = 0;
1320 OCMStub([engine flutterTextInputView:inputView
1321 updateEditingClient:0
1322 withState:[OCMArg isNotNil]])
1323 .andDo(^(NSInvocation* invocation) {
1324 updateCount++;
1325 });
1326
1327 [inputView setMarkedText:@"marked text" selectedRange:NSMakeRange(0, 1)];
1328 // updateEditingClient fires in response to setMarkedText.
1329 XCTAssertEqual(updateCount, 1);
1330
1331 UIScribbleInteraction* scribbleInteraction =
1332 [[UIScribbleInteraction alloc] initWithDelegate:inputView];
1333
1334 [inputView scribbleInteractionWillBeginWriting:scribbleInteraction];
1335 [inputView setMarkedText:@"during writing" selectedRange:NSMakeRange(1, 2)];
1336 // updateEditingClient does not fire in response to setMarkedText during a scribble interaction.
1337 XCTAssertEqual(updateCount, 1);
1338
1339 [inputView scribbleInteractionDidFinishWriting:scribbleInteraction];
1341 [inputView setMarkedText:@"marked text" selectedRange:NSMakeRange(0, 1)];
1342 // updateEditingClient fires in response to setMarkedText.
1343 XCTAssertEqual(updateCount, 2);
1344
1345 inputView.scribbleFocusStatus = FlutterScribbleFocusStatusFocusing;
1346 [inputView setMarkedText:@"during focus" selectedRange:NSMakeRange(1, 2)];
1347 // updateEditingClient does not fire in response to setMarkedText during a scribble-initiated
1348 // focus.
1349 XCTAssertEqual(updateCount, 2);
1350
1351 inputView.scribbleFocusStatus = FlutterScribbleFocusStatusFocused;
1352 [inputView setMarkedText:@"after focus" selectedRange:NSMakeRange(2, 3)];
1353 // updateEditingClient does not fire in response to setMarkedText after a scribble-initiated
1354 // focus.
1355 XCTAssertEqual(updateCount, 2);
1356
1357 inputView.scribbleFocusStatus = FlutterScribbleFocusStatusUnfocused;
1358 [inputView setMarkedText:@"marked text" selectedRange:NSMakeRange(0, 1)];
1359 // updateEditingClient fires in response to setMarkedText.
1360 XCTAssertEqual(updateCount, 3);
1361 }
1362}

◆ testSetPlatformViewClient

- (void) testSetPlatformViewClient
implementation

Definition at line 92 of file FlutterTextInputPluginTest.mm.

2828 {
2829 FlutterViewController* flutterViewController = [[FlutterViewController alloc] init];
2830 FlutterTextInputPlugin* myInputPlugin = [[FlutterTextInputPlugin alloc] initWithDelegate:engine];
2831 myInputPlugin.viewController = flutterViewController;
2832
2833 FlutterMethodCall* setClientCall = [FlutterMethodCall
2834 methodCallWithMethodName:@"TextInput.setClient"
2835 arguments:@[ [NSNumber numberWithInt:123], self.mutablePasswordTemplateCopy ]];
2836 [myInputPlugin handleMethodCall:setClientCall
2837 result:^(id _Nullable result){
2838 }];
2839 UIView* activeView = myInputPlugin.textInputView;
2840 XCTAssertNotNil(activeView.superview, @"activeView must be added to the view hierarchy.");
2841 FlutterMethodCall* setPlatformViewClientCall = [FlutterMethodCall
2842 methodCallWithMethodName:@"TextInput.setPlatformViewClient"
2843 arguments:@{@"platformViewId" : [NSNumber numberWithLong:456]}];
2844 [myInputPlugin handleMethodCall:setPlatformViewClientCall
2845 result:^(id _Nullable result){
2846 }];
2847 XCTAssertNil(activeView.superview, @"activeView must be removed from view hierarchy.");
2848}

◆ testSettingKeyboardTypeNoneDisablesSystemKeyboard

- (void) testSettingKeyboardTypeNoneDisablesSystemKeyboard
implementation

Definition at line 92 of file FlutterTextInputPluginTest.mm.

324 {
325 NSDictionary* config = self.mutableTemplateCopy;
326 [config setValue:@{@"name" : @"TextInputType.none"} forKey:@"inputType"];
327 [self setClientId:123 configuration:config];
328
329 // Verify the view's inputViewController is not nil;
331
332 [config setValue:@{@"name" : @"TextInputType.url"} forKey:@"inputType"];
333 [self setClientId:124 configuration:config];
334 XCTAssertNotNil(textInputPlugin.activeView);
336}
UIInputViewController * inputViewController()

◆ testStandardEditActions

- (void) testStandardEditActions
implementation

Definition at line 92 of file FlutterTextInputPluginTest.mm.

505 {
506 NSDictionary* config = self.mutableTemplateCopy;
507 [self setClientId:123 configuration:config];
508 NSArray<FlutterTextInputView*>* inputFields = self.installedInputViews;
509 FlutterTextInputView* inputView = inputFields[0];
510
511 [inputView insertText:@"aaaa"];
512 [inputView selectAll:nil];
513 [inputView cut:nil];
514 [inputView insertText:@"bbbb"];
515 XCTAssertTrue([inputView canPerformAction:@selector(paste:) withSender:nil]);
516 [inputView paste:nil];
517 [inputView selectAll:nil];
518 [inputView copy:nil];
519 [inputView paste:nil];
520 [inputView selectAll:nil];
521 [inputView delete:nil];
522 [inputView paste:nil];
523 [inputView paste:nil];
524
525 UITextRange* range = [FlutterTextRange rangeWithNSRange:NSMakeRange(0, 30)];
526 NSString* substring = [inputView textInRange:range];
527 XCTAssertEqualObjects(substring, @"bbbbaaaabbbbaaaa");
528}
NSString * textInRange:(UITextRange *range)

◆ testSystemOnlyAddingPartialComposedCharacter

- (void) testSystemOnlyAddingPartialComposedCharacter
implementation

Definition at line 92 of file FlutterTextInputPluginTest.mm.

567 {
568 NSDictionary* config = self.mutableTemplateCopy;
569 [self setClientId:123 configuration:config];
570 NSArray<FlutterTextInputView*>* inputFields = self.installedInputViews;
571 FlutterTextInputView* inputView = inputFields[0];
572
573 [inputView insertText:@"👨‍👩‍👧‍👦"];
574 [inputView deleteBackward];
575
576 // Insert the first unichar in the emoji.
577 [inputView insertText:[@"👨‍👩‍👧‍👦" substringWithRange:NSMakeRange(0, 1)]];
578 [inputView insertText:@"아"];
579
580 XCTAssertEqualObjects(inputView.text, @"👨‍👩‍👧‍👦아");
581
582 // Deleting 아.
583 [inputView deleteBackward];
584 // 👨‍👩‍👧‍👦 should be the current string.
585
586 [inputView insertText:@"😀"];
587 [inputView deleteBackward];
588 // Insert the first unichar in the emoji.
589 [inputView insertText:[@"😀" substringWithRange:NSMakeRange(0, 1)]];
590 [inputView insertText:@"아"];
591 XCTAssertEqualObjects(inputView.text, @"👨‍👩‍👧‍👦😀아");
592
593 // Deleting 아.
594 [inputView deleteBackward];
595 // 👨‍👩‍👧‍👦😀 should be the current string.
596
597 [inputView deleteBackward];
598 // Insert the first unichar in the emoji.
599 [inputView insertText:[@"😀" substringWithRange:NSMakeRange(0, 1)]];
600 [inputView insertText:@"아"];
601
602 XCTAssertEqualObjects(inputView.text, @"👨‍👩‍👧‍👦😀아");
603}

◆ testTextChangesDoNotTriggerUpdateEditingClient

- (void) testTextChangesDoNotTriggerUpdateEditingClient
implementation

Definition at line 92 of file FlutterTextInputPluginTest.mm.

1175 {
1176 FlutterTextInputView* inputView = [[FlutterTextInputView alloc] initWithOwner:textInputPlugin];
1177
1178 __block int updateCount = 0;
1179 OCMStub([engine flutterTextInputView:inputView updateEditingClient:0 withState:[OCMArg isNotNil]])
1180 .andDo(^(NSInvocation* invocation) {
1181 updateCount++;
1182 });
1183
1184 [inputView.text setString:@"BEFORE"];
1185 XCTAssertEqual(updateCount, 0);
1186
1187 inputView.markedTextRange = nil;
1188 inputView.selectedTextRange = nil;
1189 XCTAssertEqual(updateCount, 1);
1190
1191 // Text changes don't trigger an update.
1192 XCTAssertEqual(updateCount, 1);
1193 [inputView setTextInputState:@{@"text" : @"AFTER"}];
1194 XCTAssertEqual(updateCount, 1);
1195 [inputView setTextInputState:@{@"text" : @"AFTER"}];
1196 XCTAssertEqual(updateCount, 1);
1197
1198 // Selection changes don't trigger an update.
1199 [inputView
1200 setTextInputState:@{@"text" : @"SELECTION", @"selectionBase" : @0, @"selectionExtent" : @3}];
1201 XCTAssertEqual(updateCount, 1);
1202 [inputView
1203 setTextInputState:@{@"text" : @"SELECTION", @"selectionBase" : @1, @"selectionExtent" : @3}];
1204 XCTAssertEqual(updateCount, 1);
1205
1206 // Composing region changes don't trigger an update.
1207 [inputView
1208 setTextInputState:@{@"text" : @"COMPOSING", @"composingBase" : @1, @"composingExtent" : @2}];
1209 XCTAssertEqual(updateCount, 1);
1210 [inputView
1211 setTextInputState:@{@"text" : @"COMPOSING", @"composingBase" : @1, @"composingExtent" : @3}];
1212 XCTAssertEqual(updateCount, 1);
1213}

◆ testTextChangesDoNotTriggerUpdateEditingClientWithDelta

- (void) testTextChangesDoNotTriggerUpdateEditingClientWithDelta
implementation

Definition at line 92 of file FlutterTextInputPluginTest.mm.

1215 {
1216 FlutterTextInputView* inputView = [[FlutterTextInputView alloc] initWithOwner:textInputPlugin];
1217 inputView.enableDeltaModel = YES;
1218
1219 __block int updateCount = 0;
1220 OCMStub([engine flutterTextInputView:inputView updateEditingClient:0 withDelta:[OCMArg isNotNil]])
1221 .andDo(^(NSInvocation* invocation) {
1222 updateCount++;
1223 });
1224
1225 [inputView.text setString:@"BEFORE"];
1226 [self flushScheduledAsyncBlocks];
1227 XCTAssertEqual(updateCount, 0);
1228
1229 inputView.markedTextRange = nil;
1230 inputView.selectedTextRange = nil;
1231 [self flushScheduledAsyncBlocks];
1232 XCTAssertEqual(updateCount, 1);
1233
1234 // Text changes don't trigger an update.
1235 XCTAssertEqual(updateCount, 1);
1236 [inputView setTextInputState:@{@"text" : @"AFTER"}];
1237 [self flushScheduledAsyncBlocks];
1238 XCTAssertEqual(updateCount, 1);
1239
1240 [inputView setTextInputState:@{@"text" : @"AFTER"}];
1241 [self flushScheduledAsyncBlocks];
1242 XCTAssertEqual(updateCount, 1);
1243
1244 // Selection changes don't trigger an update.
1245 [inputView
1246 setTextInputState:@{@"text" : @"SELECTION", @"selectionBase" : @0, @"selectionExtent" : @3}];
1247 [self flushScheduledAsyncBlocks];
1248 XCTAssertEqual(updateCount, 1);
1249
1250 [inputView
1251 setTextInputState:@{@"text" : @"SELECTION", @"selectionBase" : @1, @"selectionExtent" : @3}];
1252 [self flushScheduledAsyncBlocks];
1253 XCTAssertEqual(updateCount, 1);
1254
1255 // Composing region changes don't trigger an update.
1256 [inputView
1257 setTextInputState:@{@"text" : @"COMPOSING", @"composingBase" : @1, @"composingExtent" : @2}];
1258 [self flushScheduledAsyncBlocks];
1259 XCTAssertEqual(updateCount, 1);
1260
1261 [inputView
1262 setTextInputState:@{@"text" : @"COMPOSING", @"composingBase" : @1, @"composingExtent" : @3}];
1263 [self flushScheduledAsyncBlocks];
1264 XCTAssertEqual(updateCount, 1);
1265}

◆ testTextEditingDeltasAreBatchedAndForwardedToFramework

- (void) testTextEditingDeltasAreBatchedAndForwardedToFramework
implementation

Definition at line 92 of file FlutterTextInputPluginTest.mm.

963 {
964 // Setup
965 FlutterTextInputView* inputView = [[FlutterTextInputView alloc] initWithOwner:textInputPlugin];
966 inputView.enableDeltaModel = YES;
967
968 // Expected call.
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;
988 }]]);
989
990 // Simulate user input.
991 [inputView insertText:@"-"];
992 [inputView deleteBackward];
993 [inputView insertText:@"—"];
994
995 [self flushScheduledAsyncBlocks];
996 OCMVerifyAll(engine);
997}

◆ testTextEditingDeltasAreGeneratedOnSetMarkedTextDeletion

- (void) testTextEditingDeltasAreGeneratedOnSetMarkedTextDeletion
implementation

Definition at line 92 of file FlutterTextInputPluginTest.mm.

1070 {
1071 FlutterTextInputView* inputView = [[FlutterTextInputView alloc] initWithOwner:textInputPlugin];
1072 inputView.enableDeltaModel = YES;
1073
1074 __block int updateCount = 0;
1075 OCMStub([engine flutterTextInputView:inputView updateEditingClient:0 withDelta:[OCMArg isNotNil]])
1076 .andDo(^(NSInvocation* invocation) {
1077 updateCount++;
1078 });
1079
1080 [inputView.text setString:@"Some initial text"];
1081 [self flushScheduledAsyncBlocks];
1082 XCTAssertEqual(updateCount, 0);
1083
1084 UITextRange* range = [FlutterTextRange rangeWithNSRange:NSMakeRange(13, 4)];
1085 inputView.markedTextRange = range;
1086 inputView.selectedTextRange = nil;
1087 [self flushScheduledAsyncBlocks];
1088 XCTAssertEqual(updateCount, 1);
1089
1090 [inputView setMarkedText:@"tex" selectedRange:NSMakeRange(0, 1)];
1091 OCMVerify([engine
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);
1101 }]]);
1102 [self flushScheduledAsyncBlocks];
1103 XCTAssertEqual(updateCount, 2);
1104}

◆ testTextEditingDeltasAreGeneratedOnSetMarkedTextInsertion

- (void) testTextEditingDeltasAreGeneratedOnSetMarkedTextInsertion
implementation

Definition at line 92 of file FlutterTextInputPluginTest.mm.

1034 {
1035 FlutterTextInputView* inputView = [[FlutterTextInputView alloc] initWithOwner:textInputPlugin];
1036 inputView.enableDeltaModel = YES;
1037
1038 __block int updateCount = 0;
1039 OCMStub([engine flutterTextInputView:inputView updateEditingClient:0 withDelta:[OCMArg isNotNil]])
1040 .andDo(^(NSInvocation* invocation) {
1041 updateCount++;
1042 });
1043
1044 [inputView.text setString:@"Some initial text"];
1045 [self flushScheduledAsyncBlocks];
1046 XCTAssertEqual(updateCount, 0);
1047
1048 UITextRange* range = [FlutterTextRange rangeWithNSRange:NSMakeRange(13, 4)];
1049 inputView.markedTextRange = range;
1050 inputView.selectedTextRange = nil;
1051 [self flushScheduledAsyncBlocks];
1052 XCTAssertEqual(updateCount, 1);
1053
1054 [inputView setMarkedText:@"text." selectedRange:NSMakeRange(0, 1)];
1055 OCMVerify([engine
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);
1065 }]]);
1066 [self flushScheduledAsyncBlocks];
1067 XCTAssertEqual(updateCount, 2);
1068}

◆ testTextEditingDeltasAreGeneratedOnSetMarkedTextReplacement

- (void) testTextEditingDeltasAreGeneratedOnSetMarkedTextReplacement
implementation

Definition at line 92 of file FlutterTextInputPluginTest.mm.

999 {
1000 FlutterTextInputView* inputView = [[FlutterTextInputView alloc] initWithOwner:textInputPlugin];
1001 inputView.enableDeltaModel = YES;
1002
1003 __block int updateCount = 0;
1004 OCMStub([engine flutterTextInputView:inputView updateEditingClient:0 withDelta:[OCMArg isNotNil]])
1005 .andDo(^(NSInvocation* invocation) {
1006 updateCount++;
1007 });
1008
1009 [inputView.text setString:@"Some initial text"];
1010 XCTAssertEqual(updateCount, 0);
1011
1012 UITextRange* range = [FlutterTextRange rangeWithNSRange:NSMakeRange(13, 4)];
1013 inputView.markedTextRange = range;
1014 inputView.selectedTextRange = nil;
1015 [self flushScheduledAsyncBlocks];
1016 XCTAssertEqual(updateCount, 1);
1017
1018 [inputView setMarkedText:@"new marked text." selectedRange:NSMakeRange(0, 1)];
1019 OCMVerify([engine
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);
1029 }]]);
1030 [self flushScheduledAsyncBlocks];
1031 XCTAssertEqual(updateCount, 2);
1032}

◆ testTextEditingDeltasAreGeneratedOnTextInput

- (void) testTextEditingDeltasAreGeneratedOnTextInput
implementation

Definition at line 92 of file FlutterTextInputPluginTest.mm.

834 {
835 FlutterTextInputView* inputView = [[FlutterTextInputView alloc] initWithOwner:textInputPlugin];
836 inputView.enableDeltaModel = YES;
837
838 __block int updateCount = 0;
839
840 [inputView insertText:@"text to insert"];
841 OCMExpect(
842 [engine
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);
852 }]])
853 .andDo(^(NSInvocation* invocation) {
854 updateCount++;
855 });
856 XCTAssertEqual(updateCount, 0);
857
858 [self flushScheduledAsyncBlocks];
859
860 // Update the framework exactly once.
861 XCTAssertEqual(updateCount, 1);
862
863 [inputView deleteBackward];
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"]
872 intValue] == 13) &&
873 ([[state[@"deltas"] objectAtIndex:0][@"deltaEnd"]
874 intValue] == 14);
875 }]])
876 .andDo(^(NSInvocation* invocation) {
877 updateCount++;
878 });
879 [self flushScheduledAsyncBlocks];
880 XCTAssertEqual(updateCount, 2);
881
882 inputView.selectedTextRange = [FlutterTextRange rangeWithNSRange:NSMakeRange(0, 1)];
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"]
891 intValue] == -1) &&
892 ([[state[@"deltas"] objectAtIndex:0][@"deltaEnd"]
893 intValue] == -1);
894 }]])
895 .andDo(^(NSInvocation* invocation) {
896 updateCount++;
897 });
898 [self flushScheduledAsyncBlocks];
899 XCTAssertEqual(updateCount, 3);
900
901 [inputView replaceRange:[FlutterTextRange rangeWithNSRange:NSMakeRange(0, 1)]
902 withText:@"replace text"];
903 OCMExpect(
904 [engine
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);
914 }]])
915 .andDo(^(NSInvocation* invocation) {
916 updateCount++;
917 });
918 [self flushScheduledAsyncBlocks];
919 XCTAssertEqual(updateCount, 4);
920
921 [inputView setMarkedText:@"marked text" selectedRange:NSMakeRange(0, 1)];
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"]
930 intValue] == 12) &&
931 ([[state[@"deltas"] objectAtIndex:0][@"deltaEnd"]
932 intValue] == 12);
933 }]])
934 .andDo(^(NSInvocation* invocation) {
935 updateCount++;
936 });
937 [self flushScheduledAsyncBlocks];
938 XCTAssertEqual(updateCount, 5);
939
940 [inputView unmarkText];
941 OCMExpect([engine
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] ==
950 -1) &&
951 ([[state[@"deltas"] objectAtIndex:0][@"deltaEnd"] intValue] ==
952 -1);
953 }]])
954 .andDo(^(NSInvocation* invocation) {
955 updateCount++;
956 });
957 [self flushScheduledAsyncBlocks];
958
959 XCTAssertEqual(updateCount, 6);
960 OCMVerifyAll(engine);
961}

◆ testTextInRange

- (void) testTextInRange
implementation

Definition at line 92 of file FlutterTextInputPluginTest.mm.

487 {
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;
492 FlutterTextInputView* inputView = inputFields[0];
493
494 [inputView insertText:@"test"];
495
496 UITextRange* range = [FlutterTextRange rangeWithNSRange:NSMakeRange(0, 20)];
497 NSString* substring = [inputView textInRange:range];
498 XCTAssertEqual(substring.length, 4ul);
499
500 range = [FlutterTextRange rangeWithNSRange:NSMakeRange(10, 20)];
501 substring = [inputView textInRange:range];
502 XCTAssertEqual(substring.length, 0ul);
503}

◆ testTextRangeFromPositionMatchesUITextViewBehavior

- (void) testTextRangeFromPositionMatchesUITextViewBehavior
implementation

Definition at line 92 of file FlutterTextInputPluginTest.mm.

474 {
475 FlutterTextInputView* inputView = [[FlutterTextInputView alloc] initWithOwner:textInputPlugin];
478
479 FlutterTextRange* flutterRange = (FlutterTextRange*)[inputView textRangeFromPosition:fromPosition
480 toPosition:toPosition];
481 NSRange range = flutterRange.range;
482
483 XCTAssertEqual(range.location, 0ul);
484 XCTAssertEqual(range.length, 2ul);
485}

◆ testUITextInputAccessibilityNotHiddenWhenShowed

- (void) testUITextInputAccessibilityNotHiddenWhenShowed
implementation

Definition at line 92 of file FlutterTextInputPluginTest.mm.

2667 {
2668 [self setClientId:123 configuration:self.mutableTemplateCopy];
2669
2670 // Send show text input method call.
2671 [self setTextInputShow];
2672 // Find all the FlutterTextInputViews we created.
2673 NSArray<FlutterTextInputView*>* inputFields = self.installedInputViews;
2674
2675 // The input view should not be hidden.
2676 XCTAssertEqual([inputFields count], 1u);
2677
2678 // Send hide text input method call.
2679 [self setTextInputHide];
2680
2681 inputFields = self.installedInputViews;
2682
2683 // The input view should be hidden.
2684 XCTAssertEqual([inputFields count], 0u);
2685}

◆ testUITextInputAvoidUnnecessaryUndateEditingClientCalls

- (void) testUITextInputAvoidUnnecessaryUndateEditingClientCalls
implementation

Definition at line 92 of file FlutterTextInputPluginTest.mm.

1267 {
1268 FlutterTextInputView* inputView = [[FlutterTextInputView alloc] initWithOwner:textInputPlugin];
1269
1270 __block int updateCount = 0;
1271 OCMStub([engine flutterTextInputView:inputView updateEditingClient:0 withState:[OCMArg isNotNil]])
1272 .andDo(^(NSInvocation* invocation) {
1273 updateCount++;
1274 });
1275
1276 [inputView unmarkText];
1277 // updateEditingClient shouldn't fire as the text is already unmarked.
1278 XCTAssertEqual(updateCount, 0);
1279
1280 [inputView setMarkedText:@"marked text" selectedRange:NSMakeRange(0, 1)];
1281 // updateEditingClient fires in response to setMarkedText.
1282 XCTAssertEqual(updateCount, 1);
1283
1284 [inputView unmarkText];
1285 // updateEditingClient fires in response to unmarkText.
1286 XCTAssertEqual(updateCount, 2);
1287}

◆ testUITextInputCallsUpdateEditingStateOnce

- (void) testUITextInputCallsUpdateEditingStateOnce
implementation

Definition at line 92 of file FlutterTextInputPluginTest.mm.

1108 {
1109 FlutterTextInputView* inputView = [[FlutterTextInputView alloc] initWithOwner:textInputPlugin];
1110
1111 __block int updateCount = 0;
1112 OCMStub([engine flutterTextInputView:inputView updateEditingClient:0 withState:[OCMArg isNotNil]])
1113 .andDo(^(NSInvocation* invocation) {
1114 updateCount++;
1115 });
1116
1117 [inputView insertText:@"text to insert"];
1118 // Update the framework exactly once.
1119 XCTAssertEqual(updateCount, 1);
1120
1121 [inputView deleteBackward];
1122 XCTAssertEqual(updateCount, 2);
1123
1124 inputView.selectedTextRange = [FlutterTextRange rangeWithNSRange:NSMakeRange(0, 1)];
1125 XCTAssertEqual(updateCount, 3);
1126
1127 [inputView replaceRange:[FlutterTextRange rangeWithNSRange:NSMakeRange(0, 1)]
1128 withText:@"replace text"];
1129 XCTAssertEqual(updateCount, 4);
1130
1131 [inputView setMarkedText:@"marked text" selectedRange:NSMakeRange(0, 1)];
1132 XCTAssertEqual(updateCount, 5);
1133
1134 [inputView unmarkText];
1135 XCTAssertEqual(updateCount, 6);
1136}

◆ testUITextInputCallsUpdateEditingStateWithDeltaOnce

- (void) testUITextInputCallsUpdateEditingStateWithDeltaOnce
implementation

Definition at line 92 of file FlutterTextInputPluginTest.mm.

1138 {
1139 FlutterTextInputView* inputView = [[FlutterTextInputView alloc] initWithOwner:textInputPlugin];
1140 inputView.enableDeltaModel = YES;
1141
1142 __block int updateCount = 0;
1143 OCMStub([engine flutterTextInputView:inputView updateEditingClient:0 withDelta:[OCMArg isNotNil]])
1144 .andDo(^(NSInvocation* invocation) {
1145 updateCount++;
1146 });
1147
1148 [inputView insertText:@"text to insert"];
1149 [self flushScheduledAsyncBlocks];
1150 // Update the framework exactly once.
1151 XCTAssertEqual(updateCount, 1);
1152
1153 [inputView deleteBackward];
1154 [self flushScheduledAsyncBlocks];
1155 XCTAssertEqual(updateCount, 2);
1156
1157 inputView.selectedTextRange = [FlutterTextRange rangeWithNSRange:NSMakeRange(0, 1)];
1158 [self flushScheduledAsyncBlocks];
1159 XCTAssertEqual(updateCount, 3);
1160
1161 [inputView replaceRange:[FlutterTextRange rangeWithNSRange:NSMakeRange(0, 1)]
1162 withText:@"replace text"];
1163 [self flushScheduledAsyncBlocks];
1164 XCTAssertEqual(updateCount, 4);
1165
1166 [inputView setMarkedText:@"marked text" selectedRange:NSMakeRange(0, 1)];
1167 [self flushScheduledAsyncBlocks];
1168 XCTAssertEqual(updateCount, 5);
1169
1170 [inputView unmarkText];
1171 [self flushScheduledAsyncBlocks];
1172 XCTAssertEqual(updateCount, 6);
1173}

◆ testUpdateEditingClientNegativeSelection

- (void) testUpdateEditingClientNegativeSelection
implementation

Definition at line 92 of file FlutterTextInputPluginTest.mm.

1364 {
1365 FlutterTextInputView* inputView = [[FlutterTextInputView alloc] initWithOwner:textInputPlugin];
1366
1367 [inputView.text setString:@"SELECTION"];
1368 inputView.markedTextRange = nil;
1369 inputView.selectedTextRange = nil;
1370
1371 [inputView setTextInputState:@{
1372 @"text" : @"SELECTION",
1373 @"selectionBase" : @-1,
1374 @"selectionExtent" : @-1
1375 }];
1376 [inputView updateEditingState];
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);
1382 }]]);
1383
1384 // Returns (0, 0) when either end goes below 0.
1385 [inputView
1386 setTextInputState:@{@"text" : @"SELECTION", @"selectionBase" : @-1, @"selectionExtent" : @1}];
1387 [inputView updateEditingState];
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);
1393 }]]);
1394
1395 [inputView
1396 setTextInputState:@{@"text" : @"SELECTION", @"selectionBase" : @1, @"selectionExtent" : @-1}];
1397 [inputView updateEditingState];
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);
1403 }]]);
1404}

◆ testUpdateEditingClientSelectionClamping

- (void) testUpdateEditingClientSelectionClamping
implementation

Definition at line 92 of file FlutterTextInputPluginTest.mm.

1406 {
1407 // Regression test for https://github.com/flutter/flutter/issues/62992.
1408 FlutterTextInputView* inputView = [[FlutterTextInputView alloc] initWithOwner:textInputPlugin];
1409
1410 [inputView.text setString:@"SELECTION"];
1411 inputView.markedTextRange = nil;
1412 inputView.selectedTextRange = nil;
1413
1414 [inputView
1415 setTextInputState:@{@"text" : @"SELECTION", @"selectionBase" : @0, @"selectionExtent" : @0}];
1416 [inputView updateEditingState];
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);
1422 }]]);
1423
1424 // Needs clamping.
1425 [inputView setTextInputState:@{
1426 @"text" : @"SELECTION",
1427 @"selectionBase" : @0,
1428 @"selectionExtent" : @9999
1429 }];
1430 [inputView updateEditingState];
1431
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);
1437 }]]);
1438
1439 // No clamping needed, but in reverse direction.
1440 [inputView
1441 setTextInputState:@{@"text" : @"SELECTION", @"selectionBase" : @1, @"selectionExtent" : @0}];
1442 [inputView updateEditingState];
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);
1448 }]]);
1449
1450 // Both ends need clamping.
1451 [inputView setTextInputState:@{
1452 @"text" : @"SELECTION",
1453 @"selectionBase" : @9999,
1454 @"selectionExtent" : @9999
1455 }];
1456 [inputView updateEditingState];
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);
1462 }]]);
1463}

◆ testUpdateFirstRectForRange

- (void) testUpdateFirstRectForRange
implementation

Definition at line 92 of file FlutterTextInputPluginTest.mm.

1503 {
1504 [self setClientId:123 configuration:self.mutableTemplateCopy];
1505
1507 textInputPlugin.viewController.view.frame = CGRectMake(0, 0, 0, 0);
1508
1509 [inputView
1510 setTextInputState:@{@"text" : @"COMPOSING", @"composingBase" : @1, @"composingExtent" : @3}];
1511
1512 CGRect kInvalidFirstRect = CGRectMake(-1, -1, 9999, 9999);
1513 FlutterTextRange* range = [FlutterTextRange rangeWithNSRange:NSMakeRange(0, 1)];
1514 // yOffset = 200.
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 ];
1517 // This matrix can be generated by running this dart code snippet:
1518 // Matrix4.identity()..scale(3.0)..rotateZ(math.pi/2)..translate(1.0, 2.0,
1519 // 3.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)
1523 ];
1524
1525 // Invalid since we don't have the transform or the rect.
1526 XCTAssertTrue(CGRectEqualToRect(kInvalidFirstRect, [inputView firstRectForRange:range]));
1527
1528 [inputView setEditableTransform:yOffsetMatrix];
1529 // Invalid since we don't have the rect.
1530 XCTAssertTrue(CGRectEqualToRect(kInvalidFirstRect, [inputView firstRectForRange:range]));
1531
1532 // Valid rect and transform.
1533 CGRect testRect = CGRectMake(0, 0, 100, 100);
1534 [inputView setMarkedRect:testRect];
1535
1536 CGRect finalRect = CGRectOffset(testRect, 0, 200);
1537 XCTAssertTrue(CGRectEqualToRect(finalRect, [inputView firstRectForRange:range]));
1538 // Idempotent.
1539 XCTAssertTrue(CGRectEqualToRect(finalRect, [inputView firstRectForRange:range]));
1540
1541 // Use an invalid matrix:
1542 [inputView setEditableTransform:zeroMatrix];
1543 // Invalid matrix is invalid.
1544 XCTAssertTrue(CGRectEqualToRect(kInvalidFirstRect, [inputView firstRectForRange:range]));
1545 XCTAssertTrue(CGRectEqualToRect(kInvalidFirstRect, [inputView firstRectForRange:range]));
1546
1547 // Revert the invalid matrix change.
1548 [inputView setEditableTransform:yOffsetMatrix];
1549 [inputView setMarkedRect:testRect];
1550 XCTAssertTrue(CGRectEqualToRect(finalRect, [inputView firstRectForRange:range]));
1551
1552 // Use an invalid rect:
1553 [inputView setMarkedRect:kInvalidFirstRect];
1554 // Invalid marked rect is invalid.
1555 XCTAssertTrue(CGRectEqualToRect(kInvalidFirstRect, [inputView firstRectForRange:range]));
1556 XCTAssertTrue(CGRectEqualToRect(kInvalidFirstRect, [inputView firstRectForRange:range]));
1557
1558 // Use a 3d affine transform that does 3d-scaling, z-index rotating and 3d translation.
1559 [inputView setEditableTransform:affineMatrix];
1560 [inputView setMarkedRect:testRect];
1561 XCTAssertTrue(
1562 CGRectEqualToRect(CGRectMake(-306, 3, 300, 300), [inputView firstRectForRange:range]));
1563
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;
1569 // Moving the input view within the FlutterView shouldn't affect the coordinates,
1570 // since the framework sends us global coordinates.
1571 XCTAssertTrue(CGRectEqualToRect(CGRectMake(-306 - 113, 3 - 119, 300, 300),
1572 [inputView firstRectForRange:range]));
1573}
void setMarkedRect:(CGRect markedRect)
void setEditableTransform:(NSArray *matrix)
const CGRect kInvalidFirstRect
SeparatedVector2 offset

◆ testUpdateSecureTextEntry

- (void) testUpdateSecureTextEntry
implementation

Definition at line 92 of file FlutterTextInputPluginTest.mm.

742 {
743 NSDictionary* config = self.mutableTemplateCopy;
744 [config setValue:@"YES" forKey:@"obscureText"];
745 [self setClientId:123 configuration:config];
746
747 NSArray<FlutterTextInputView*>* inputFields = self.installedInputViews;
748 FlutterTextInputView* inputView = OCMPartialMock(inputFields[0]);
749
750 __block int callCount = 0;
751 OCMStub([inputView reloadInputViews]).andDo(^(NSInvocation* invocation) {
752 callCount++;
753 });
754
755 XCTAssertTrue(inputView.isSecureTextEntry);
756
757 config = self.mutableTemplateCopy;
758 [config setValue:@"NO" forKey:@"obscureText"];
759 [self updateConfig:config];
760
761 XCTAssertEqual(callCount, 1);
762 XCTAssertFalse(inputView.isSecureTextEntry);
763}

◆ testVisiblePasswordUseAlphanumeric

- (void) testVisiblePasswordUseAlphanumeric
implementation

Definition at line 92 of file FlutterTextInputPluginTest.mm.

310 {
311 NSDictionary* config = self.mutableTemplateCopy;
312 [config setValue:@{@"name" : @"TextInputType.visiblePassword"} forKey:@"inputType"];
313 [self setClientId:123 configuration:config];
314
315 // Find all the FlutterTextInputViews we created.
316 NSArray<FlutterTextInputView*>* inputFields = self.installedInputViews;
317
318 FlutterTextInputView* inputView = inputFields[0];
319
320 // Verify keyboardType is set to the value specified in config.
321 XCTAssertEqual(inputView.keyboardType, UIKeyboardTypeASCIICapable);
322}

◆ testWillNotCrashWhenViewControllerIsNil

- (void) testWillNotCrashWhenViewControllerIsNil
implementation

Definition at line 92 of file FlutterTextInputPluginTest.mm.

201 {
202 FlutterEngine* flutterEngine = [[FlutterEngine alloc] init];
203 FlutterTextInputPlugin* inputPlugin =
204 [[FlutterTextInputPlugin alloc] initWithDelegate:(id<FlutterTextInputDelegate>)flutterEngine];
205 XCTAssertNil(inputPlugin.viewController);
206 FlutterMethodCall* methodCall = [FlutterMethodCall methodCallWithMethodName:@"TextInput.show"
207 arguments:nil];
208 XCTestExpectation* expectation = [[XCTestExpectation alloc] initWithDescription:@"result called"];
209
210 [inputPlugin handleMethodCall:methodCall
211 result:^(id _Nullable result) {
212 XCTAssertNil(result);
213 [expectation fulfill];
214 }];
215 XCTAssertNil(inputPlugin.activeView);
216 [self waitForExpectations:@[ expectation ] timeout:1.0];
217}

◆ updateConfig:

- (void) updateConfig: (NSDictionary*)  config
implementation

Definition at line 92 of file FlutterTextInputPluginTest.mm.

191 :(NSDictionary*)config {
192 FlutterMethodCall* updateConfigCall =
193 [FlutterMethodCall methodCallWithMethodName:@"TextInput.updateConfig" arguments:config];
194 [textInputPlugin handleMethodCall:updateConfigCall
195 result:^(id _Nullable result){
196 }];
197}

◆ viewsVisibleToAutofill

- (NSArray< FlutterTextInputView * > *) viewsVisibleToAutofill
implementation

Definition at line 92 of file FlutterTextInputPluginTest.mm.

2274 {
2275 return [self.installedInputViews
2276 filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"isVisibleToAutofill == YES"]];
2277}

The documentation for this class was generated from the following file: