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

Commit 239b294

Browse files
committed
[ios]fix text input rotor accessiblity
1 parent bf538a0 commit 239b294

File tree

4 files changed

+136
-0
lines changed

4 files changed

+136
-0
lines changed

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1187,6 +1187,8 @@ - (BOOL)canPerformAction:(SEL)action withSender:(id)sender {
11871187
} else if (action == @selector(copy:) || action == @selector(cut:) ||
11881188
action == @selector(delete:)) {
11891189
return [self textInRange:_selectedTextRange].length > 0;
1190+
} else if (action == @selector(select:) || action == @selector(selectAll:)) {
1191+
return self.hasText;
11901192
}
11911193
return [super canPerformAction:action withSender:sender];
11921194
}

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

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -527,6 +527,21 @@ - (void)testStandardEditActions {
527527
XCTAssertEqualObjects(substring, @"bbbbaaaabbbbaaaa");
528528
}
529529

530+
- (void)testCanPerformActionForSelectActions {
531+
NSDictionary* config = self.mutableTemplateCopy;
532+
[self setClientId:123 configuration:config];
533+
NSArray<FlutterTextInputView*>* inputFields = self.installedInputViews;
534+
FlutterTextInputView* inputView = inputFields[0];
535+
536+
XCTAssertFalse([inputView canPerformAction:@selector(select:) withSender:nil]);
537+
XCTAssertFalse([inputView canPerformAction:@selector(selectAll:) withSender:nil]);
538+
539+
[inputView insertText:@"aaaa"];
540+
541+
XCTAssertTrue([inputView canPerformAction:@selector(select:) withSender:nil]);
542+
XCTAssertTrue([inputView canPerformAction:@selector(selectAll:) withSender:nil]);
543+
}
544+
530545
- (void)testDeletingBackward {
531546
NSDictionary* config = self.mutableTemplateCopy;
532547
[self setClientId:123 configuration:config];

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

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@
1616

1717
const float kFloatCompareEpsilon = 0.001;
1818

19+
@interface TextInputSemanticsObject (Test)
20+
- (UIView<UITextInput>*)textInputSurrogate;
21+
@end
22+
1923
@interface SemanticsObjectTest : XCTestCase
2024
@end
2125

@@ -1069,4 +1073,89 @@ - (void)testTextInputSemanticsObject {
10691073
XCTAssertEqual([object accessibilityTraits], UIAccessibilityTraitNone);
10701074
}
10711075

1076+
- (void)testTextInputSemanticsObject_canPerformAction {
1077+
fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(
1078+
new flutter::testing::MockAccessibilityBridge());
1079+
fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
1080+
1081+
flutter::SemanticsNode node;
1082+
node.label = "foo";
1083+
node.flags = static_cast<int32_t>(flutter::SemanticsFlags::kIsTextField) |
1084+
static_cast<int32_t>(flutter::SemanticsFlags::kIsReadOnly);
1085+
TextInputSemanticsObject* object = [[TextInputSemanticsObject alloc] initWithBridge:bridge uid:0];
1086+
[object setSemanticsNode:&node];
1087+
[object accessibilityBridgeDidFinishUpdate];
1088+
1089+
id textInputSurrogate = OCMClassMock([UIResponder class]);
1090+
id partialSemanticsObject = OCMPartialMock(object);
1091+
OCMStub([partialSemanticsObject textInputSurrogate]).andReturn(textInputSurrogate);
1092+
1093+
OCMExpect([textInputSurrogate canPerformAction:[OCMArg anySelector] withSender:OCMOCK_ANY])
1094+
.andReturn(YES);
1095+
XCTAssertTrue([partialSemanticsObject canPerformAction:@selector(copy:) withSender:nil]);
1096+
1097+
OCMExpect([textInputSurrogate canPerformAction:[OCMArg anySelector] withSender:OCMOCK_ANY])
1098+
.andReturn(NO);
1099+
XCTAssertFalse([partialSemanticsObject canPerformAction:@selector(copy:) withSender:nil]);
1100+
}
1101+
1102+
- (void)testTextInputSemanticsObject_editActions {
1103+
fml::WeakPtrFactory<flutter::AccessibilityBridgeIos> factory(
1104+
new flutter::testing::MockAccessibilityBridge());
1105+
fml::WeakPtr<flutter::AccessibilityBridgeIos> bridge = factory.GetWeakPtr();
1106+
1107+
flutter::SemanticsNode node;
1108+
node.label = "foo";
1109+
node.flags = static_cast<int32_t>(flutter::SemanticsFlags::kIsTextField) |
1110+
static_cast<int32_t>(flutter::SemanticsFlags::kIsReadOnly);
1111+
TextInputSemanticsObject* object = [[TextInputSemanticsObject alloc] initWithBridge:bridge uid:0];
1112+
[object setSemanticsNode:&node];
1113+
[object accessibilityBridgeDidFinishUpdate];
1114+
1115+
id textInputSurrogate = OCMClassMock([UIResponder class]);
1116+
id partialSemanticsObject = OCMPartialMock(object);
1117+
OCMStub([partialSemanticsObject textInputSurrogate]).andReturn(textInputSurrogate);
1118+
1119+
XCTestExpectation* copyExpectation =
1120+
[self expectationWithDescription:@"Surrogate's copy method is called."];
1121+
XCTestExpectation* cutExpectation =
1122+
[self expectationWithDescription:@"Surrogate's cut method is called."];
1123+
XCTestExpectation* pasteExpectation =
1124+
[self expectationWithDescription:@"Surrogate's paste method is called."];
1125+
XCTestExpectation* selectExpectation =
1126+
[self expectationWithDescription:@"Surrogate's select method is called."];
1127+
XCTestExpectation* selectAllExpectation =
1128+
[self expectationWithDescription:@"Surrogate's selectAll method is called."];
1129+
XCTestExpectation* deleteExpectation =
1130+
[self expectationWithDescription:@"Surrogate's delete method is called."];
1131+
1132+
OCMStub([textInputSurrogate copy:OCMOCK_ANY]).andDo(^(NSInvocation* invocation) {
1133+
[copyExpectation fulfill];
1134+
});
1135+
OCMStub([textInputSurrogate cut:OCMOCK_ANY]).andDo(^(NSInvocation* invocation) {
1136+
[cutExpectation fulfill];
1137+
});
1138+
OCMStub([textInputSurrogate paste:OCMOCK_ANY]).andDo(^(NSInvocation* invocation) {
1139+
[pasteExpectation fulfill];
1140+
});
1141+
OCMStub([textInputSurrogate select:OCMOCK_ANY]).andDo(^(NSInvocation* invocation) {
1142+
[selectExpectation fulfill];
1143+
});
1144+
OCMStub([textInputSurrogate selectAll:OCMOCK_ANY]).andDo(^(NSInvocation* invocation) {
1145+
[selectAllExpectation fulfill];
1146+
});
1147+
OCMStub([textInputSurrogate delete:OCMOCK_ANY]).andDo(^(NSInvocation* invocation) {
1148+
[deleteExpectation fulfill];
1149+
});
1150+
1151+
[partialSemanticsObject copy:nil];
1152+
[partialSemanticsObject cut:nil];
1153+
[partialSemanticsObject paste:nil];
1154+
[partialSemanticsObject select:nil];
1155+
[partialSemanticsObject selectAll:nil];
1156+
[partialSemanticsObject delete:nil];
1157+
1158+
[self waitForExpectationsWithTimeout:1 handler:nil];
1159+
}
1160+
10721161
@end

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

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -462,4 +462,34 @@ - (BOOL)hasText {
462462
return [[self textInputSurrogate] hasText];
463463
}
464464

465+
#pragma mark - UIResponder overrides
466+
467+
- (void)cut:(id)sender {
468+
[[self textInputSurrogate] cut:sender];
469+
}
470+
471+
- (void)copy:(id)sender {
472+
[[self textInputSurrogate] copy:sender];
473+
}
474+
475+
- (void)paste:(id)sender {
476+
[[self textInputSurrogate] paste:sender];
477+
}
478+
479+
- (void)select:(id)sender {
480+
[[self textInputSurrogate] select:sender];
481+
}
482+
483+
- (void)selectAll:(id)sender {
484+
[[self textInputSurrogate] selectAll:sender];
485+
}
486+
487+
- (void)delete:(id)sender {
488+
[[self textInputSurrogate] delete:sender];
489+
}
490+
491+
- (BOOL)canPerformAction:(SEL)action withSender:(id)sender {
492+
return [[self textInputSurrogate] canPerformAction:action withSender:sender];
493+
}
494+
465495
@end

0 commit comments

Comments
 (0)