refactor(ioscompanion): name the empty-editable-field sentinel for what it is

This commit is contained in:
pj committed 2026-06-07 01:12:04 +05:30
1 parent 622b7c5cc4
commit a6229afda7
2 files changed
+8 -40

No files matched your search

+8 -19
View File
@@ -14,25 +14,14 @@ type rawFrame struct {
Height float64 `json:"height"` Height float64 `json:"height"`
} }
// emptyFieldValueSentinel is what the accessibility bridge reports as the
// AXValue of an empty editable field. It is bridge state, not app content, so
// it maps to an empty value rather than surfacing as literal field text.
const emptyFieldValueSentinel = "Invalid"
// rawElement is one entry in the flat describe-all dump returned by the // rawElement is one entry in the flat describe-all dump returned by the
// simulator companion. Only the fields the mapper consumes are declared; // simulator companion. Only the fields the mapper consumes are declared;
// unknown fields are ignored. // unknown fields are ignored.
// unresolvedValueSentinel is what the accessibility bridge reports for a value
// it cannot resolve yet (typically during app cold start). It is bridge state,
// not app content.
const unresolvedValueSentinel = "Invalid"
// hasUnresolvedValues reports whether any element in the flat dump still
// carries the bridge's unresolved-value sentinel.
func hasUnresolvedValues(dump []byte) bool {
for _, element := range decodeDump(dump) {
if stringValue(element.AXValue) == unresolvedValueSentinel {
return true
}
}
return false
}
type rawElement struct { type rawElement struct {
Frame rawFrame `json:"frame"` Frame rawFrame `json:"frame"`
AXUniqueID *string `json:"AXUniqueId"` AXUniqueID *string `json:"AXUniqueId"`
@@ -108,9 +97,9 @@ func mapElement(element *rawElement) (treeNode, bool) {
} }
value := stringValue(element.AXValue) value := stringValue(element.AXValue)
if value == unresolvedValueSentinel { if value == emptyFieldValueSentinel {
// Bridge state, not app content: surfacing it would show phantom // An empty editable field reads as this sentinel through the bridge;
// field values in snapshots taken while the bridge catches up. // it is not app content, so treat the field as empty.
value = "" value = ""
} }
label := stringValue(element.AXLabel) label := stringValue(element.AXLabel)
@@ -224,27 +224,6 @@ func TestRootIsFlatWithAllChildren(t *testing.T) {
} }
} }
func TestHasUnresolvedValues(t *testing.T) {
cases := []struct {
name string
dump string
want bool
}{
{name: "sentinel value", dump: `[{"type":"TextField","AXValue":"Invalid","frame":{"x":0,"y":0,"width":1,"height":1}}]`, want: true},
{name: "clean values", dump: `[{"type":"TextField","AXValue":"hello","frame":{"x":0,"y":0,"width":1,"height":1}}]`, want: false},
{name: "empty value", dump: `[{"type":"TextField","AXValue":"","frame":{"x":0,"y":0,"width":1,"height":1}}]`, want: false},
{name: "empty dump", dump: `[]`, want: false},
{name: "malformed dump", dump: `nope`, want: false},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
if got := hasUnresolvedValues([]byte(c.dump)); got != c.want {
t.Fatalf("got %v, want %v", got, c.want)
}
})
}
}
func TestMapHierarchySentinelValueMapsAsEmpty(t *testing.T) { func TestMapHierarchySentinelValueMapsAsEmpty(t *testing.T) {
dump := `[{"type":"TextField","AXUniqueId":"F","AXLabel":"Email","AXValue":"Invalid","frame":{"x":0,"y":0,"width":10,"height":10},"enabled":true}]` dump := `[{"type":"TextField","AXUniqueId":"F","AXLabel":"Email","AXValue":"Invalid","frame":{"x":0,"y":0,"width":10,"height":10},"enabled":true}]`
mapped, err := MapHierarchy([]byte(dump), 100, 100) mapped, err := MapHierarchy([]byte(dump), 100, 100)