Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit 7f47689

Browse files
add tests
1 parent 846462d commit 7f47689

File tree

6 files changed

+188
-13
lines changed

6 files changed

+188
-13
lines changed

lib/web_ui/lib/src/engine/text_editing/text_editing.dart

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -391,12 +391,13 @@ class AutofillInfo {
391391
const TextCapitalizationConfig.defaultCapitalization()}) {
392392
assert(autofill != null); // ignore: unnecessary_null_comparison
393393
final String uniqueIdentifier = autofill.readString('uniqueIdentifier');
394-
final List<dynamic> hintsList = autofill.readList('hints');
394+
final List<dynamic>? hintsList = autofill.tryList('hints');
395+
final String? firstHint = (hintList == null || hintList.isEmpty) ? null : hintList.first;
395396
final EditingState editingState =
396397
EditingState.fromFrameworkMessage(autofill.readJson('editingValue'));
397398
return AutofillInfo(
398399
uniqueIdentifier: uniqueIdentifier,
399-
autofillHint: hintsList.isNotEmpty ? BrowserAutofillHints.instance.flutterToEngine(hintsList[0] as String) : null,
400+
autofillHint: (firstHint != null) ? BrowserAutofillHints.instance.flutterToEngine(firstHint) : null,
400401
editingState: editingState,
401402
placeholder: autofill.tryString('hintText'),
402403
textCapitalization: textCapitalization,

shell/platform/android/io/flutter/embedding/engine/systemchannels/TextInputChannel.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -469,10 +469,11 @@ public static Autofill fromJson(@NonNull JSONObject json)
469469
final JSONObject editingState = json.getJSONObject("editingValue");
470470
final String[] autofillHints = new String[hints.length()];
471471

472-
for (int i = 0; i < hintList.length; i++) {
472+
for (int i = 0; i < hints.length(); i++) {
473473
autofillHints[i] = translateAutofillHint(hints.getString(i));
474474
}
475-
return new Autofill(uniqueIdentifier, autofillHints, hintText, TextEditState.fromJson(editingState));
475+
return new Autofill(
476+
uniqueIdentifier, autofillHints, hintText, TextEditState.fromJson(editingState));
476477
}
477478

478479
public final String uniqueIdentifier;

shell/platform/android/io/flutter/plugin/editing/TextInputPlugin.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -658,7 +658,7 @@ public void didChangeEditingState(
658658
//
659659
// ### Keep the AFM updated
660660
//
661-
// The autofill session connected to The AFM keeps a copy of the current state for each reported
661+
// The autofill session connected to the AFM keeps a copy of the current state for each reported
662662
// field in "AutofillVirtualStructure" (instead of holding a reference to those fields), so the
663663
// AFM needs to be notified when text changes if the client was part of the
664664
// "AutofillVirtualStructure" previously reported to the AFM. This step is essential for

shell/platform/android/test/io/flutter/plugin/editing/TextInputPluginTest.java

Lines changed: 155 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -657,6 +657,137 @@ public void showTextInput_textInputTypeNone() {
657657
}
658658

659659
// -------- Start: Autofill Tests -------
660+
@Test
661+
public void autofill_enabledByDefault() {
662+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
663+
return;
664+
}
665+
FlutterView testView = new FlutterView(RuntimeEnvironment.application);
666+
TextInputChannel textInputChannel = new TextInputChannel(mock(DartExecutor.class));
667+
TextInputPlugin textInputPlugin =
668+
new TextInputPlugin(testView, textInputChannel, mock(PlatformViewsController.class));
669+
final TextInputChannel.Configuration.Autofill autofill =
670+
new TextInputChannel.Configuration.Autofill(
671+
"1", new String[] {}, null, new TextInputChannel.TextEditState("", 0, 0, -1, -1));
672+
673+
final TextInputChannel.Configuration config =
674+
new TextInputChannel.Configuration(
675+
false,
676+
false,
677+
true,
678+
true,
679+
TextInputChannel.TextCapitalization.NONE,
680+
null,
681+
null,
682+
null,
683+
autofill,
684+
null);
685+
686+
textInputPlugin.setTextInputClient(
687+
0,
688+
new TextInputChannel.Configuration(
689+
false,
690+
false,
691+
true,
692+
true,
693+
TextInputChannel.TextCapitalization.NONE,
694+
null,
695+
null,
696+
null,
697+
autofill,
698+
new TextInputChannel.Configuration[] {config}));
699+
700+
final ViewStructure viewStructure = mock(ViewStructure.class);
701+
final ViewStructure[] children = {mock(ViewStructure.class), mock(ViewStructure.class)};
702+
703+
when(viewStructure.newChild(anyInt()))
704+
.thenAnswer(invocation -> children[(int) invocation.getArgument(0)]);
705+
706+
textInputPlugin.onProvideAutofillVirtualStructure(viewStructure, 0);
707+
708+
verify(viewStructure).newChild(0);
709+
710+
verify(children[0]).setAutofillId(any(), eq("1".hashCode()));
711+
verify(children[0]).setAutofillHints(aryEq(new String[] {}));
712+
verify(children[0]).setDimens(anyInt(), anyInt(), anyInt(), anyInt(), gt(0), gt(0));
713+
}
714+
715+
@Test
716+
public void autofill_canBeDisabled() {
717+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
718+
return;
719+
}
720+
FlutterView testView = new FlutterView(RuntimeEnvironment.application);
721+
TextInputChannel textInputChannel = new TextInputChannel(mock(DartExecutor.class));
722+
TextInputPlugin textInputPlugin =
723+
new TextInputPlugin(testView, textInputChannel, mock(PlatformViewsController.class));
724+
final TextInputChannel.Configuration.Autofill autofill =
725+
new TextInputChannel.Configuration.Autofill(
726+
"1", new String[] {}, null, new TextInputChannel.TextEditState("", 0, 0, -1, -1));
727+
728+
final TextInputChannel.Configuration config =
729+
new TextInputChannel.Configuration(
730+
false,
731+
false,
732+
true,
733+
true,
734+
TextInputChannel.TextCapitalization.NONE,
735+
null,
736+
null,
737+
null,
738+
null,
739+
null);
740+
741+
textInputPlugin.setTextInputClient(0, config);
742+
743+
final ViewStructure viewStructure = mock(ViewStructure.class);
744+
745+
textInputPlugin.onProvideAutofillVirtualStructure(viewStructure, 0);
746+
747+
verify(viewStructure, times(0)).newChild(anyInt());
748+
}
749+
750+
@Test
751+
public void autofill_hintText() {
752+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
753+
return;
754+
}
755+
FlutterView testView = new FlutterView(RuntimeEnvironment.application);
756+
TextInputChannel textInputChannel = new TextInputChannel(mock(DartExecutor.class));
757+
TextInputPlugin textInputPlugin =
758+
new TextInputPlugin(testView, textInputChannel, mock(PlatformViewsController.class));
759+
final TextInputChannel.Configuration.Autofill autofill =
760+
new TextInputChannel.Configuration.Autofill(
761+
"1",
762+
new String[] {},
763+
"placeholder",
764+
new TextInputChannel.TextEditState("", 0, 0, -1, -1));
765+
766+
final TextInputChannel.Configuration config =
767+
new TextInputChannel.Configuration(
768+
false,
769+
false,
770+
true,
771+
true,
772+
TextInputChannel.TextCapitalization.NONE,
773+
null,
774+
null,
775+
null,
776+
autofill,
777+
null);
778+
779+
textInputPlugin.setTextInputClient(0, config);
780+
781+
final ViewStructure viewStructure = mock(ViewStructure.class);
782+
final ViewStructure[] children = {mock(ViewStructure.class), mock(ViewStructure.class)};
783+
784+
when(viewStructure.newChild(anyInt()))
785+
.thenAnswer(invocation -> children[(int) invocation.getArgument(0)]);
786+
787+
textInputPlugin.onProvideAutofillVirtualStructure(viewStructure, 0);
788+
verify(children[0]).setHint("placeholder");
789+
}
790+
660791
@Test
661792
public void autofill_onProvideVirtualViewStructure() {
662793
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
@@ -669,11 +800,15 @@ public void autofill_onProvideVirtualViewStructure() {
669800
new TextInputPlugin(testView, textInputChannel, mock(PlatformViewsController.class));
670801
final TextInputChannel.Configuration.Autofill autofill1 =
671802
new TextInputChannel.Configuration.Autofill(
672-
"1", new String[] {"HINT1"}, new TextInputChannel.TextEditState("", 0, 0, -1, -1));
803+
"1",
804+
new String[] {"HINT1"},
805+
"placeholder1",
806+
new TextInputChannel.TextEditState("", 0, 0, -1, -1));
673807
final TextInputChannel.Configuration.Autofill autofill2 =
674808
new TextInputChannel.Configuration.Autofill(
675809
"2",
676810
new String[] {"HINT2", "EXTRA"},
811+
null,
677812
new TextInputChannel.TextEditState("", 0, 0, -1, -1));
678813

679814
final TextInputChannel.Configuration config1 =
@@ -729,10 +864,12 @@ public void autofill_onProvideVirtualViewStructure() {
729864
verify(children[0]).setAutofillId(any(), eq("1".hashCode()));
730865
verify(children[0]).setAutofillHints(aryEq(new String[] {"HINT1"}));
731866
verify(children[0]).setDimens(anyInt(), anyInt(), anyInt(), anyInt(), gt(0), gt(0));
867+
verify(children[0]).setHint("placeholder1");
732868

733869
verify(children[1]).setAutofillId(any(), eq("2".hashCode()));
734870
verify(children[1]).setAutofillHints(aryEq(new String[] {"HINT2", "EXTRA"}));
735871
verify(children[1]).setDimens(anyInt(), anyInt(), anyInt(), anyInt(), gt(0), gt(0));
872+
verify(children[1], times(0)).setHint(any());
736873
}
737874

738875
@Test
@@ -747,7 +884,10 @@ public void autofill_onProvideVirtualViewStructure_single() {
747884
new TextInputPlugin(testView, textInputChannel, mock(PlatformViewsController.class));
748885
final TextInputChannel.Configuration.Autofill autofill =
749886
new TextInputChannel.Configuration.Autofill(
750-
"1", new String[] {"HINT1"}, new TextInputChannel.TextEditState("", 0, 0, -1, -1));
887+
"1",
888+
new String[] {"HINT1"},
889+
"placeholder",
890+
new TextInputChannel.TextEditState("", 0, 0, -1, -1));
751891

752892
// Autofill should still work without AutofillGroup.
753893
textInputPlugin.setTextInputClient(
@@ -776,6 +916,7 @@ public void autofill_onProvideVirtualViewStructure_single() {
776916

777917
verify(children[0]).setAutofillId(any(), eq("1".hashCode()));
778918
verify(children[0]).setAutofillHints(aryEq(new String[] {"HINT1"}));
919+
verify(children[0]).setHint("placeholder");
779920
// Verifies that the child has a non-zero size.
780921
verify(children[0]).setDimens(anyInt(), anyInt(), anyInt(), anyInt(), gt(0), gt(0));
781922
}
@@ -796,11 +937,15 @@ public void autofill_testLifeCycle() {
796937
// Set up an autofill scenario with 2 fields.
797938
final TextInputChannel.Configuration.Autofill autofill1 =
798939
new TextInputChannel.Configuration.Autofill(
799-
"1", new String[] {"HINT1"}, new TextInputChannel.TextEditState("", 0, 0, -1, -1));
940+
"1",
941+
new String[] {"HINT1"},
942+
"placeholder1",
943+
new TextInputChannel.TextEditState("", 0, 0, -1, -1));
800944
final TextInputChannel.Configuration.Autofill autofill2 =
801945
new TextInputChannel.Configuration.Autofill(
802946
"2",
803947
new String[] {"HINT2", "EXTRA"},
948+
null,
804949
new TextInputChannel.TextEditState("", 0, 0, -1, -1));
805950

806951
final TextInputChannel.Configuration config1 =
@@ -921,11 +1066,13 @@ public void autofill_testAutofillUpdatesTheFramework() {
9211066
new TextInputChannel.Configuration.Autofill(
9221067
"1",
9231068
new String[] {"HINT1"},
1069+
null,
9241070
new TextInputChannel.TextEditState("field 1", 0, 0, -1, -1));
9251071
final TextInputChannel.Configuration.Autofill autofill2 =
9261072
new TextInputChannel.Configuration.Autofill(
9271073
"2",
9281074
new String[] {"HINT2", "EXTRA"},
1075+
null,
9291076
new TextInputChannel.TextEditState("field 2", 0, 0, -1, -1));
9301077

9311078
final TextInputChannel.Configuration config1 =
@@ -1008,11 +1155,15 @@ public void autofill_testSetTextIpnutClientUpdatesSideFields() {
10081155
// Set up an autofill scenario with 2 fields.
10091156
final TextInputChannel.Configuration.Autofill autofill1 =
10101157
new TextInputChannel.Configuration.Autofill(
1011-
"1", new String[] {"HINT1"}, new TextInputChannel.TextEditState("", 0, 0, -1, -1));
1158+
"1",
1159+
new String[] {"HINT1"},
1160+
"null",
1161+
new TextInputChannel.TextEditState("", 0, 0, -1, -1));
10121162
final TextInputChannel.Configuration.Autofill autofill2 =
10131163
new TextInputChannel.Configuration.Autofill(
10141164
"2",
10151165
new String[] {"HINT2", "EXTRA"},
1166+
"null",
10161167
new TextInputChannel.TextEditState(
10171168
"Unfocused fields need love like everything does", 0, 0, -1, -1));
10181169

shell/platform/darwin/ios/framework/Source/FlutterTextInputPlugin.mm

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ static UIReturnKeyType ToUIReturnKeyType(NSString* inputType) {
159159
}
160160

161161
static UITextContentType ToUITextContentType(NSArray<NSString*>* hints) {
162-
if (!hints || hits.count == 0) {
162+
if (!hints || hints.count == 0) {
163163
// If no hints are specified, use the default content type nil.
164164
return nil;
165165
}
@@ -347,13 +347,15 @@ typedef NS_ENUM(NSInteger, FlutterAutofillType) {
347347

348348
static BOOL isFieldPasswordRelated(NSDictionary* configuration) {
349349
if (@available(iOS 10.0, *)) {
350+
// Autofill is explicitly disabled if the id isn't present.
351+
if (!autofillIdFromDictionary(configuration)) {
352+
return NO;
353+
}
354+
350355
BOOL isSecureTextEntry = [configuration[kSecureTextEntry] boolValue];
351356
if (isSecureTextEntry)
352357
return YES;
353358

354-
if (!autofillIdFromDictionary(configuration)) {
355-
return NO;
356-
}
357359
NSDictionary* autofill = configuration[kAutofillProperties];
358360
UITextContentType contentType = ToUITextContentType(autofill[kAutofillHints]);
359361

shell/platform/darwin/ios/framework/Source/FlutterTextInputPluginTest.mm

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -633,6 +633,26 @@ - (void)commitAutofillContextAndVerify {
633633

634634
#pragma mark - Autofill - Tests
635635

636+
- (void)testDisablingAutofillOnInputClient {
637+
NSDictionary* config = self.mutableTemplateCopy;
638+
[config setValue:@"YES" forKey:@"obscureText"];
639+
640+
[self setClientId:123 configuration:config];
641+
642+
FlutterTextInputView* inputView = self.installedInputViews[0];
643+
XCTAssertEqualObjects(inputView.textContentType, @"");
644+
}
645+
646+
- (void)testAutofillEnabledByDefault {
647+
NSDictionary* config = self.mutableTemplateCopy;
648+
[config setValue:@"NO" forKey:@"obscureText"];
649+
650+
[self setClientId:123 configuration:config];
651+
652+
FlutterTextInputView* inputView = self.installedInputViews[0];
653+
XCTAssertNil(inputView.textContentType);
654+
}
655+
636656
- (void)testAutofillContext {
637657
NSMutableDictionary* field1 = self.mutableTemplateCopy;
638658

0 commit comments

Comments
 (0)