mirror of
https://github.com/priyanshujain/sanderling.git
synced 2026-10-02 19:17:10 +00:00
fix(hierarchy): rank spatial-fallback matches by specificity
The bounds-containment fallback returned the first pre-order match, so a screen-sized container could win over the intended small element. Matches are now ordered smallest-area first; equal-area matches keep pre-order, preserving the iOS-flat equal-bounds sibling pattern.
This commit is contained in:
1 parent
29ec13adcd
commit
c356340563
2 files changed
+82
No files matched your search
@@ -28,6 +28,7 @@ import (
|
||||
"fmt"
|
||||
"maps"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
@@ -426,9 +427,22 @@ func (n *Node) scopedNodes(accept func(*Element) bool) []*Node {
|
||||
result = append(result, candidate)
|
||||
}
|
||||
}
|
||||
// Spatial containment alone lets a large container outrank the intended
|
||||
// small element; the most specific (smallest) match wins instead.
|
||||
sortBySpecificity(result)
|
||||
return result
|
||||
}
|
||||
|
||||
// sortBySpecificity orders nodes ascending by bounds area, so the smallest
|
||||
// (most specific) containing match comes first. Equal-area nodes keep their
|
||||
// pre-order position.
|
||||
func sortBySpecificity(nodes []*Node) {
|
||||
sort.SliceStable(nodes, func(i, j int) bool {
|
||||
return nodes[i].Bounds.Width()*nodes[i].Bounds.Height() <
|
||||
nodes[j].Bounds.Width()*nodes[j].Bounds.Height()
|
||||
})
|
||||
}
|
||||
|
||||
func collectMatches(node *Node, accept func(*Element) bool, result *[]*Node) {
|
||||
if accept(&node.Element) {
|
||||
*result = append(*result, node)
|
||||
|
||||
@@ -856,6 +856,74 @@ func TestIOSFlatSpatialScopeExcludesOutsideBounds(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// spatialSpecificityDump scopes a leaf testTag (iOS-flat pattern) over a
|
||||
// screen where both a screen-sized container and the small element inside it
|
||||
// match the same attribute, plus two equal-bounds siblings for tie ordering.
|
||||
const spatialSpecificityDump = `{
|
||||
"attributes": {"bounds": "[0,0][402,874]"},
|
||||
"children": [
|
||||
{
|
||||
"attributes": {"resource-id": "FormScreen", "bounds": "[0,62][402,840]"},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"attributes": {"resource-id": "FieldContainer", "accessibilityText": "Amount", "bounds": "[0,62][402,840]"},
|
||||
"children": [
|
||||
{
|
||||
"attributes": {"resource-id": "AmountField", "accessibilityText": "Amount", "bounds": "[34,125][368,173]"},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"attributes": {"resource-id": "FirstTab", "accessibilityText": "Tab", "bounds": "[20,297][382,345]"},
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"attributes": {"resource-id": "SecondTab", "accessibilityText": "Tab", "bounds": "[20,297][382,345]"},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}`
|
||||
|
||||
func TestSpatialFallbackPrefersSmallestContainingMatch(t *testing.T) {
|
||||
tree, err := Parse(spatialSpecificityDump)
|
||||
if err != nil {
|
||||
t.Fatalf("Parse: %v", err)
|
||||
}
|
||||
screen := tree.FindNode("id:FormScreen")
|
||||
if screen == nil {
|
||||
t.Fatal("expected FormScreen node")
|
||||
}
|
||||
// Both FieldContainer (screen-sized, earlier in pre-order) and
|
||||
// AmountField (small) match; the most specific match must win.
|
||||
node := screen.Find("desc:Amount")
|
||||
if node == nil {
|
||||
t.Fatal("expected a spatial-fallback match")
|
||||
}
|
||||
if node.ResourceID != "AmountField" {
|
||||
t.Fatalf("expected the smallest containing match AmountField, got id=%q", node.ResourceID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSpatialFallbackEqualAreaKeepsPreOrder(t *testing.T) {
|
||||
tree, err := Parse(spatialSpecificityDump)
|
||||
if err != nil {
|
||||
t.Fatalf("Parse: %v", err)
|
||||
}
|
||||
screen := tree.FindNode("id:FormScreen")
|
||||
if screen == nil {
|
||||
t.Fatal("expected FormScreen node")
|
||||
}
|
||||
node := screen.Find("desc:Tab")
|
||||
if node == nil {
|
||||
t.Fatal("expected a spatial-fallback match")
|
||||
}
|
||||
if node.ResourceID != "FirstTab" {
|
||||
t.Fatalf("equal-area matches must keep pre-order, got id=%q", node.ResourceID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIOSFlatStructuralChildStillPreferred(t *testing.T) {
|
||||
tree, _ := Parse(iosFlatDump)
|
||||
submit := tree.FindNode("id:LoginSubmit")
|
||||
|
||||
Reference in new issue
Block a user