fix(sidecar): surface visible text on iOS static elements

Static text and button strings live in the accessibility label on
iOS, so the text attribute came through empty and every balance
extractor parsed to zero, silently disarming both folio properties.
Non-editable elements now fall back title, value, then label;
editable fields keep value-only so an empty field's caption does not
read as content.
This commit is contained in:
pj committed 2026-06-05 18:37:57 +05:30
1 parent 33a0924f7e
commit d6188df667
2 files changed
+36 -3

No files matched your search

@@ -878,12 +878,20 @@ internal fun iosAxElementToTreeNode(element: hierarchy.AXElement): Map<String, A
val elementType = element.elementType
val title = element.title.orEmpty()
val value = element.value.orEmpty()
val label = element.label.orEmpty()
val editable = elementType in IOS_EDITABLE_ELEMENT_TYPES
val checked = elementType in IOS_CHECKABLE_ELEMENT_TYPES && value == "1"
// text carries the element's visible string, matching what Android
// surfaces in its text attribute. Editable fields expose only their
// typed content: their label is the field caption (e.g. "Email"), and
// leaking it into text would make an empty field look filled. Static
// text and buttons carry the visible string in the label.
val text = if (editable) value else title.ifEmpty { value }.ifEmpty { label }
val attributes = linkedMapOf(
"accessibilityText" to element.label.orEmpty(),
"accessibilityText" to label,
"title" to title,
"value" to value,
"text" to title.ifEmpty { value },
"text" to text,
"hintText" to element.placeholderValue.orEmpty(),
"resource-id" to element.identifier.orEmpty(),
"bounds" to element.frame.boundsString,
@@ -902,6 +910,6 @@ internal fun iosAxElementToTreeNode(element: hierarchy.AXElement): Map<String, A
"focused" to element.hasFocus,
"checked" to checked,
"selected" to element.selected,
"editable" to (elementType in IOS_EDITABLE_ELEMENT_TYPES),
"editable" to editable,
)
}
@@ -66,6 +66,31 @@ class IosHierarchyTest {
assertEquals("StaticText", attributes["class"])
}
@Test
fun staticTextFallsBackToLabelForText() {
val node = iosAxElementToTreeNode(element(elementType = 48, label = "$1,234.00"))
@Suppress("UNCHECKED_CAST")
val attributes = node["attributes"] as Map<String, String>
assertEquals("$1,234.00", attributes["text"])
}
@Test
fun buttonFallsBackToLabelForText() {
val node = iosAxElementToTreeNode(element(elementType = 9, label = "Sign in"))
@Suppress("UNCHECKED_CAST")
val attributes = node["attributes"] as Map<String, String>
assertEquals("Sign in", attributes["text"])
}
@Test
fun emptyTextFieldDoesNotLeakLabelIntoText() {
val node = iosAxElementToTreeNode(element(elementType = 49, label = "Email"))
@Suppress("UNCHECKED_CAST")
val attributes = node["attributes"] as Map<String, String>
assertEquals("", attributes["text"])
assertEquals("Email", attributes["accessibilityText"])
}
@Test
fun switchOnIsChecked() {
val node = iosAxElementToTreeNode(element(elementType = 40, value = "1"))