Skip to content

Commit

Permalink
add test
Browse files Browse the repository at this point in the history
  • Loading branch information
LongCatIsLooong committed Apr 3, 2020
1 parent 8f828bf commit 60af9a5
Show file tree
Hide file tree
Showing 5 changed files with 154 additions and 45 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -536,7 +536,7 @@ - (void)updateEditingClient:(int)client withState:(NSDictionary*)state {

- (void)updateEditingClient:(int)client withState:(NSDictionary*)state withTag:(NSString*)tag {
[_textInputChannel.get() invokeMethod:@"TextInputClient.updateEditingStateWithTag"
arguments:@[ @(client), state, tag ]];
arguments:@[ @(client), @{tag : state} ]];
}

- (void)updateFloatingCursor:(FlutterFloatingCursorDragState)state
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@
#include "flutter/shell/platform/darwin/common/framework/Headers/FlutterChannels.h"
#include "flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputDelegate.h"

#if FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG
FLUTTER_EXPORT
#endif
@interface FlutterTextInputPlugin : NSObject

@property(nonatomic, assign) id<FlutterTextInputDelegate> textInputDelegate;
Expand All @@ -36,6 +39,9 @@
@end

/** A range of text in the buffer of a Flutter text editing widget. */
#if FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG
FLUTTER_EXPORT
#endif
@interface FlutterTextRange : UITextRange <NSCopying>

@property(nonatomic, readonly) NSRange range;
Expand All @@ -44,4 +50,34 @@

@end

#if FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG
FLUTTER_EXPORT
#endif
@interface FlutterTextInputView : UIView <UITextInput>

// UITextInput
@property(nonatomic, readonly) NSMutableString* text;
@property(nonatomic, readonly) NSMutableString* markedText;
@property(readwrite, copy) UITextRange* selectedTextRange;
@property(nonatomic, strong) UITextRange* markedTextRange;
@property(nonatomic, copy) NSDictionary* markedTextStyle;
@property(nonatomic, assign) id<UITextInputDelegate> inputDelegate;

// UITextInputTraits
@property(nonatomic) UITextAutocapitalizationType autocapitalizationType;
@property(nonatomic) UITextAutocorrectionType autocorrectionType;
@property(nonatomic) UITextSpellCheckingType spellCheckingType;
@property(nonatomic) BOOL enablesReturnKeyAutomatically;
@property(nonatomic) UIKeyboardAppearance keyboardAppearance;
@property(nonatomic) UIKeyboardType keyboardType;
@property(nonatomic) UIReturnKeyType returnKeyType;
@property(nonatomic, getter=isSecureTextEntry) BOOL secureTextEntry;
@property(nonatomic) UITextSmartQuotesType smartQuotesType API_AVAILABLE(ios(11.0));
@property(nonatomic) UITextSmartDashesType smartDashesType API_AVAILABLE(ios(11.0));
@property(nonatomic, copy) UITextContentType textContentType API_AVAILABLE(ios(10.0));

@property(nonatomic, assign) id<FlutterTextInputDelegate> textInputDelegate;

@end

#endif // SHELL_PLATFORM_IOS_FRAMEWORK_SOURCE_FLUTTERTEXTINPUTPLUGIN_H_
Original file line number Diff line number Diff line change
Expand Up @@ -88,11 +88,18 @@ static UIReturnKeyType ToUIReturnKeyType(NSString* inputType) {
return UIReturnKeyDefault;
}

// TODO(LongCatIsLooong): add translation for evey predefined
// UITextContentType.
static UITextContentType ToUITextContentType(NSArray<NSString*>* hints) {
if (hints == nil || hints.count == 0)
return @"";

return hints[0];
}

static NSString* _uniqueIdFromDictionary(NSDictionary* dictionary) {
NSDictionary* autofill = dictionary[@"autofill"];
if (autofill == nil)
return nil;
return autofill[@"uniqueIdentifier"];
return autofill == nil ? nil : autofill[@"uniqueIdentifier"];
}

#pragma mark - FlutterTextPosition
Expand Down Expand Up @@ -147,34 +154,8 @@ - (id)copyWithZone:(NSZone*)zone {

@end

@interface FlutterTextInputView : UIView <UITextInput>

// UITextInput
@property(nonatomic, readonly) NSMutableString* text;
@property(nonatomic, readonly) NSMutableString* markedText;
@property(readwrite, copy) UITextRange* selectedTextRange;
@property(nonatomic, strong) UITextRange* markedTextRange;
@property(nonatomic, copy) NSDictionary* markedTextStyle;
@property(nonatomic, assign) id<UITextInputDelegate> inputDelegate;

// UITextInputTraits
@property(nonatomic) UITextAutocapitalizationType autocapitalizationType;
@property(nonatomic) UITextAutocorrectionType autocorrectionType;
@property(nonatomic) UITextSpellCheckingType spellCheckingType;
@property(nonatomic) BOOL enablesReturnKeyAutomatically;
@property(nonatomic) UIKeyboardAppearance keyboardAppearance;
@property(nonatomic) UIKeyboardType keyboardType;
@property(nonatomic) UIReturnKeyType returnKeyType;
@property(nonatomic, getter=isSecureTextEntry) BOOL secureTextEntry;
@property(nonatomic) UITextSmartQuotesType smartQuotesType API_AVAILABLE(ios(11.0));
@property(nonatomic) UITextSmartDashesType smartDashesType API_AVAILABLE(ios(11.0));

@property(nonatomic, assign) id<FlutterTextInputDelegate> textInputDelegate;

@end

@implementation FlutterTextInputView {
NSString* _uniqueIdentifier;
NSString* _autofillId;
int _textInputClient;
const char* _selectionAffinity;
FlutterTextRange* _selectedTextRange;
Expand Down Expand Up @@ -218,15 +199,18 @@ - (void)dealloc {
[_markedTextRange release];
[_selectedTextRange release];
[_tokenizer release];
if (_uniqueIdentifier != nil)
[_uniqueIdentifier release];
[_autofillId release];
[super dealloc];
}

- (void)setTextInputClient:(int)client {
_textInputClient = client;
}

- (void)setAutofillId:(NSString*)autofillId {
_autofillId = [autofillId copy];
}

- (void)setTextInputState:(NSDictionary*)state {
NSString* newText = state[@"text"];
BOOL textChanged = ![self.text isEqualToString:newText];
Expand Down Expand Up @@ -637,10 +621,8 @@ - (void)updateEditingState {
@"text" : [NSString stringWithString:self.text],
};

if (_textInputClient == 0 && _uniqueIdentifier != nil)
[_textInputDelegate updateEditingClient:_textInputClient
withState:state
withTag:_uniqueIdentifier];
if (_textInputClient == 0 && _autofillId != nil)
[_textInputDelegate updateEditingClient:_textInputClient withState:state withTag:_autofillId];
else
[_textInputDelegate updateEditingClient:_textInputClient withState:state];
}
Expand Down Expand Up @@ -810,9 +792,13 @@ - (void)setTextInputClient:(int)client withConfiguration:(NSDictionary*)configur

for (NSDictionary* field in allFields) {
FlutterTextInputView* newInputView = [[FlutterTextInputView alloc] init];
newInputView.textInputDelegate = _textInputDelegate;
[_inputViews addObject:newInputView];

if ([clientUniqueId isEqualToString:_uniqueIdFromDictionary(field)])
NSString* autofillId = _uniqueIdFromDictionary(field);
[newInputView setAutofillId:autofillId];

if ([clientUniqueId isEqualToString:autofillId])
_activeView = newInputView;

[FlutterTextInputPlugin setupInputView:newInputView WithConfiguration:field];
Expand Down Expand Up @@ -858,14 +844,17 @@ + (void)setupInputView:(FlutterTextInputView*)inputView
inputView.autocorrectionType = autocorrect && ![autocorrect boolValue]
? UITextAutocorrectionTypeNo
: UITextAutocorrectionTypeDefault;
if (autofill == nil) {
if (@available(iOS 10.0, *))
if (@available(iOS 10.0, *)) {
if (autofill == nil) {
inputView.textContentType = @"";
} else {
if (@available(iOS 10.0, *))
inputView.textContentType = autofill[@"hints"];

[inputView setTextInputState:autofill[@"editingValue"]];
} else {
inputView.textContentType = ToUITextContentType(autofill[@"hints"]);
[inputView setTextInputState:autofill[@"editingValue"]];
// An input field needs to be visible in order to get
// autofilled when it's not the one that triggered
// autofill.
inputView.frame = CGRectMake(0, 0, 1, 1);
}
}
}

Expand All @@ -876,4 +865,5 @@ - (void)setTextInputEditingState:(NSDictionary*)state {
- (void)clearTextInputClient {
[_activeView setTextInputClient:0];
}

@end
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#import <OCMock/OCMock.h>
#import <XCTest/XCTest.h>
#include "flutter/shell/platform/darwin/common/framework/Headers/FlutterMacros.h"
#import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterEngine.h"
#include "flutter/shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.h"

FLUTTER_ASSERT_ARC

@interface FlutterTextInputPluginTest : XCTestCase
@end

@implementation FlutterTextInputPluginTest

- (void)testAutofillInputViews {
// Setup test.
id engine = OCMClassMock([FlutterEngine class]);
FlutterTextInputPlugin* textInputPlugin = [[FlutterTextInputPlugin alloc] init];
textInputPlugin.textInputDelegate = engine;

NSDictionary* template = @{
@"inputType" : @{@"name" : @"TextInuptType.text"},
@"keyboardAppearance" : @"Brightness.light",
@"obscureText" : @NO,
@"inputAction" : @"TextInputAction.unspecified",
@"smartDashesType" : @"0",
@"smartQuotesType" : @"0",
@"autocorrect" : @YES
};

NSMutableDictionary* field1 = [template mutableCopy];
[field1 setValue:@{
@"uniqueIdentifier" : @"field1",
@"hints" : @[ @"hint1" ],
@"editingValue" : @{@"text" : @""}
}
forKey:@"autofill"];

NSMutableDictionary* field2 = [template mutableCopy];
[field2 setValue:@{
@"uniqueIdentifier" : @"field2",
@"hints" : @[ @"hint2" ],
@"editingValue" : @{@"text" : @""}
}
forKey:@"autofill"];

NSMutableDictionary* config = [field1 mutableCopy];
[config setValue:@[ field1, field2 ] forKey:@"allFields"];

FlutterMethodCall* setClientCall =
[FlutterMethodCall methodCallWithMethodName:@"TextInput.setClient"
arguments:@[ @123, config ]];

[textInputPlugin handleMethodCall:setClientCall
result:^(id _Nullable result){
}];

// Find all input views in the input hider view.
NSArray<FlutterTextInputView*>* inputFields =
[[[textInputPlugin textInputView] superview] subviews];

XCTAssertEqual(inputFields.count, 2);

// Find the inactive autofillable input field.
FlutterTextInputView* inactiveView = inputFields[1];
[inactiveView replaceRange:[FlutterTextRange rangeWithNSRange:NSMakeRange(0, 0)]
withText:@"Autofilled!"];

// Verify behavior.
OCMVerify([engine updateEditingClient:0 withState:[OCMArg isNotNil] withTag:@"field2"]);

// Clean up mocks
[engine stopMocking];
}

@end
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
0D6AB6EB22BB40E700EEE540 /* FlutterEngineTest.mm in Sources */ = {isa = PBXBuildFile; fileRef = 0D6AB6E722BB40CF00EEE540 /* FlutterEngineTest.mm */; settings = {COMPILER_FLAGS = "-fobjc-arc"; }; };
0D6AB72C22BC339F00EEE540 /* libOCMock.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 0D6AB72522BC336100EEE540 /* libOCMock.a */; };
0D6AB73F22BD8F0200EEE540 /* FlutterEngineConfig.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 0D6AB73E22BD8F0200EEE540 /* FlutterEngineConfig.xcconfig */; };
3D3E3323243679E800DE8862 /* FlutterTextInputPluginTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 3D3E331C243679E800DE8862 /* FlutterTextInputPluginTest.m */; settings = {COMPILER_FLAGS = "-fobjc-arc"; }; };
/* End PBXBuildFile section */

/* Begin PBXContainerItemProxy section */
Expand Down Expand Up @@ -94,6 +95,7 @@
0D6AB6E722BB40CF00EEE540 /* FlutterEngineTest.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FlutterEngineTest.mm; sourceTree = "<group>"; };
0D6AB71722BC336100EEE540 /* OCMock.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = OCMock.xcodeproj; path = ../../../../../third_party/ocmock/Source/OCMock.xcodeproj; sourceTree = "<group>"; };
0D6AB73E22BD8F0200EEE540 /* FlutterEngineConfig.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = FlutterEngineConfig.xcconfig; sourceTree = "<group>"; };
3D3E331C243679E800DE8862 /* FlutterTextInputPluginTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FlutterTextInputPluginTest.m; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
Expand Down Expand Up @@ -175,6 +177,7 @@
isa = PBXGroup;
children = (
0D52D3B622C566D50011DEBD /* FlutterBinaryMessengerRelayTest.mm */,
3D3E331C243679E800DE8862 /* FlutterTextInputPluginTest.m */,
0D6AB6E722BB40CF00EEE540 /* FlutterEngineTest.mm */,
0D17A5BF22D78FCD0057279F /* FlutterViewControllerTest.m */,
0D4C3FAF22DF9F5300A67C70 /* FlutterPluginAppLifeCycleDelegateTest.m */,
Expand Down Expand Up @@ -387,6 +390,7 @@
buildActionMask = 2147483647;
files = (
0D6AB6EB22BB40E700EEE540 /* FlutterEngineTest.mm in Sources */,
3D3E3323243679E800DE8862 /* FlutterTextInputPluginTest.m in Sources */,
0D17A5C022D78FCD0057279F /* FlutterViewControllerTest.m in Sources */,
0D1CE5D8233430F400E5D880 /* FlutterChannelsTest.m in Sources */,
0D52D3BD22C566D50011DEBD /* FlutterBinaryMessengerRelayTest.mm in Sources */,
Expand Down

0 comments on commit 60af9a5

Please sign in to comment.