Skip to content

Commit 521b268

Browse files
authored
Update accessibility label for merged nodes (#2539)
Implement logic to merge the content descriptions for accessibility elements that mimics the behaviour of the TalkBack app. Use a comma instead of a newline symbol to join content descriptions. Fix an issue where accessibility elements inside other accessibility elements may not be accessible. Fixes https://youtrack.jetbrains.com/issue/CMP-9124/iOS-A11y-mergeDescendants-custom-contentDescription ## Release Notes ### Fixes - iOS - Fix the speaking of text in merged accessibility nodes. - Fix an issue where accessibility elements inside other accessibility elements may not be accessible.
1 parent 7aa4b8a commit 521b268

File tree

4 files changed

+330
-34
lines changed

4 files changed

+330
-34
lines changed

compose/ui/ui/src/uikitInstrumentedTest/kotlin/androidx/compose/ui/accessibility/ComponentsAccessibilitySemanticTest.kt

Lines changed: 221 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package androidx.compose.ui.accessibility
1818

1919
import androidx.compose.foundation.Image
2020
import androidx.compose.foundation.clickable
21+
import androidx.compose.foundation.layout.Box
2122
import androidx.compose.foundation.layout.Column
2223
import androidx.compose.foundation.layout.Row
2324
import androidx.compose.foundation.layout.size
@@ -46,15 +47,18 @@ import androidx.compose.ui.interop.runUIKitInstrumentedTestWithInterop
4647
import androidx.compose.ui.platform.accessibility.CMPAccessibilityTraitTextView
4748
import androidx.compose.ui.platform.testTag
4849
import androidx.compose.ui.semantics.Role
50+
import androidx.compose.ui.semantics.contentDescription
4951
import androidx.compose.ui.semantics.heading
5052
import androidx.compose.ui.semantics.isTraversalGroup
5153
import androidx.compose.ui.semantics.role
5254
import androidx.compose.ui.semantics.semantics
5355
import androidx.compose.ui.semantics.testTag
56+
import androidx.compose.ui.semantics.text
5457
import androidx.compose.ui.state.ToggleableState
5558
import androidx.compose.ui.test.assertAccessibilityTree
5659
import androidx.compose.ui.test.findNodeWithTag
5760
import androidx.compose.ui.test.runUIKitInstrumentedTest
61+
import androidx.compose.ui.text.AnnotatedString
5862
import androidx.compose.ui.text.buildAnnotatedString
5963
import androidx.compose.ui.unit.dp
6064
import androidx.compose.ui.viewinterop.UIKitInteropProperties
@@ -718,7 +722,7 @@ class ComponentsAccessibilitySemanticTest {
718722
}
719723

720724
assertAccessibilityTree {
721-
label = "Foo\nBar"
725+
label = "Foo, Bar"
722726
identifier = "row"
723727
isAccessibilityElement = true
724728
traits(UIAccessibilityTraitButton)
@@ -749,7 +753,7 @@ class ComponentsAccessibilitySemanticTest {
749753
}
750754

751755
assertAccessibilityTree {
752-
value = "Label"
756+
label = "Label"
753757
isAccessibilityElement = true
754758
traits(CMPAccessibilityTraitTextView)
755759
}
@@ -766,7 +770,7 @@ class ComponentsAccessibilitySemanticTest {
766770
}
767771

768772
assertAccessibilityTree {
769-
value = "Placeholder"
773+
label = "Placeholder"
770774
isAccessibilityElement = true
771775
traits(CMPAccessibilityTraitTextView)
772776
}
@@ -789,4 +793,218 @@ class ComponentsAccessibilitySemanticTest {
789793
traits(CMPAccessibilityTraitTextView)
790794
}
791795
}
796+
797+
@Test
798+
fun testNodeHierarchyInsideAccessibilityElementShouldNotFlatten() = runUIKitInstrumentedTest {
799+
setContent {
800+
Column(modifier = Modifier.clickable {}) {
801+
Text("Title 1")
802+
Row(modifier = Modifier.testTag("Tag 1")) {
803+
Text("Description 1")
804+
Text("Details 1")
805+
}
806+
}
807+
}
808+
809+
assertAccessibilityTree {
810+
isAccessibilityElement = true
811+
label = "Title 1, Description 1, Details 1"
812+
node {
813+
label = "Title 1"
814+
isAccessibilityElement = false
815+
}
816+
node {
817+
identifier = "Tag 1"
818+
isAccessibilityElement = false
819+
node {
820+
label = "Description 1"
821+
isAccessibilityElement = false
822+
}
823+
node {
824+
label = "Details 1"
825+
isAccessibilityElement = false
826+
}
827+
}
828+
}
829+
}
830+
831+
@Test
832+
fun testNodeHierarchyInsideTraversalGroupShouldFlatten() = runUIKitInstrumentedTest {
833+
setContent {
834+
Column {
835+
Column(modifier = Modifier.semantics { isTraversalGroup = true }) {
836+
Text("Title 1")
837+
Row(modifier = Modifier.testTag("Tag 1")) {
838+
Text("Description 1")
839+
Text("Details 1")
840+
}
841+
}
842+
Column(modifier = Modifier.semantics { isTraversalGroup = true }) {
843+
Text("Title 2")
844+
}
845+
}
846+
}
847+
848+
assertAccessibilityTree {
849+
node {
850+
node {
851+
label = "Title 1"
852+
isAccessibilityElement = true
853+
}
854+
node {
855+
label = "Description 1"
856+
isAccessibilityElement = true
857+
}
858+
node {
859+
identifier = "Tag 1"
860+
isAccessibilityElement = false
861+
}
862+
node {
863+
label = "Details 1"
864+
isAccessibilityElement = true
865+
}
866+
}
867+
node {
868+
label = "Title 2"
869+
isAccessibilityElement = true
870+
}
871+
}
872+
}
873+
874+
@Test
875+
fun testReplacedTextContent() = runUIKitInstrumentedTest {
876+
setContent {
877+
Text("Text", modifier = Modifier.semantics {
878+
text = AnnotatedString("Replaced")
879+
})
880+
}
881+
882+
assertAccessibilityTree {
883+
label = "Replaced"
884+
}
885+
}
886+
887+
@Test
888+
fun testReplacedContentWithMergedSemantics() = runUIKitInstrumentedTest {
889+
setContent {
890+
Box(modifier = Modifier.size(50.dp).semantics(mergeDescendants = true) {
891+
text = AnnotatedString("Text")
892+
contentDescription = "Description"
893+
}) {
894+
Text("Text")
895+
}
896+
}
897+
898+
assertAccessibilityTree {
899+
label = "Description, Text"
900+
isAccessibilityElement = true
901+
node {
902+
label = "Text"
903+
isAccessibilityElement = false
904+
}
905+
}
906+
}
907+
908+
@Test
909+
fun testReplacedContentWithoutMergedSemantics() = runUIKitInstrumentedTest {
910+
setContent {
911+
Box(modifier = Modifier.size(50.dp).semantics {
912+
contentDescription = "Description"
913+
}) {
914+
Text("Text")
915+
}
916+
}
917+
918+
assertAccessibilityTree {
919+
node {
920+
label = "Text"
921+
isAccessibilityElement = true
922+
}
923+
node {
924+
label = "Description"
925+
isAccessibilityElement = true
926+
}
927+
}
928+
}
929+
930+
@Test
931+
fun testContentReplacedSemanticsWithChildElement() = runUIKitInstrumentedTest {
932+
setContent {
933+
Box(modifier = Modifier.semantics(mergeDescendants = true) {
934+
text = AnnotatedString("Text")
935+
contentDescription = "Description"
936+
}) {
937+
Box(modifier = Modifier.size(50.dp).testTag("Child"))
938+
}
939+
}
940+
941+
assertAccessibilityTree {
942+
label = "Description"
943+
isAccessibilityElement = true
944+
node {
945+
identifier = "Child"
946+
}
947+
}
948+
}
949+
950+
@Test
951+
fun testEnclosedComplexContentWithMergedSemantics() = runUIKitInstrumentedTest {
952+
setContent {
953+
Column(modifier = Modifier.semantics(mergeDescendants = true) {
954+
text = AnnotatedString("Text")
955+
contentDescription = "Description"
956+
}) {
957+
Box(modifier = Modifier.size(50.dp).semantics {
958+
contentDescription = "Inner"
959+
}) {
960+
TextField(
961+
value = "",
962+
onValueChange = {},
963+
label = { Text("Label") },
964+
placeholder = { Text("Placeholder") }
965+
)
966+
}
967+
Column(modifier = Modifier.semantics(mergeDescendants = true) {}) {
968+
Text(text = "First Text")
969+
Text(text = "Second Text")
970+
}
971+
Text(text = "Text")
972+
}
973+
}
974+
975+
assertAccessibilityTree {
976+
node {
977+
isAccessibilityElement = true
978+
label = "Label"
979+
node {
980+
label = "Label"
981+
isAccessibilityElement = false
982+
}
983+
}
984+
node {
985+
label = "Description, Inner, Text"
986+
isAccessibilityElement = true
987+
node {
988+
label = "Inner"
989+
isAccessibilityElement = false
990+
}
991+
node {
992+
label = "Text"
993+
isAccessibilityElement = false
994+
}
995+
}
996+
node {
997+
label = "First Text, Second Text"
998+
isAccessibilityElement = true
999+
node {
1000+
label = "First Text"
1001+
isAccessibilityElement = false
1002+
}
1003+
node {
1004+
label = "Second Text"
1005+
isAccessibilityElement = false
1006+
}
1007+
}
1008+
}
1009+
}
7921010
}

0 commit comments

Comments
 (0)