diff --git a/internal/hierarchy/hierarchy.go b/internal/hierarchy/hierarchy.go index 02ba6d8..b19b88e 100644 --- a/internal/hierarchy/hierarchy.go +++ b/internal/hierarchy/hierarchy.go @@ -667,6 +667,31 @@ func (t *Tree) Transitional() bool { return false } +// ScreenName names the route this tree shows: the driver-set screen when the +// platform reports one (web), otherwise the resource id ending in "Screen" that +// marks the route composable. A tree carrying two different route ids is a +// cross-fade in flight and names no screen, which is the same reading +// Transitional gives it. +func (t *Tree) ScreenName() string { + if t == nil || len(t.Elements) == 0 { + return "" + } + if screen := t.Elements[0].Screen; screen != "" { + return screen + } + name := "" + for _, element := range t.Elements { + if !strings.HasSuffix(element.ResourceID, "Screen") { + continue + } + if name != "" && name != element.ResourceID { + return "" + } + name = element.ResourceID + } + return name +} + // Find returns the first element matching the selector, or nil. func (t *Tree) Find(selector string) *Element { node := t.FindNode(selector) diff --git a/internal/hierarchy/hierarchy_test.go b/internal/hierarchy/hierarchy_test.go index 02e6bd5..d742bfc 100644 --- a/internal/hierarchy/hierarchy_test.go +++ b/internal/hierarchy/hierarchy_test.go @@ -1821,3 +1821,57 @@ func TestTagMatchesTheElementItNames(t *testing.T) { }) } } + +func TestScreenNameNamesTheRouteTheTreeShows(t *testing.T) { + cases := []struct { + name string + tree string + want string + }{ + { + "driver-set screen", + `{"attributes": {"bounds": "[0,0,10,10]", "sanderling-screen": "/ledger"}, "children": []}`, + "/ledger", + }, + { + "route marker", + `{"attributes": {"bounds": "[0,0,10,10]"}, "children": [ + {"attributes": {"resource-id": "HomeScreen", "bounds": "[0,0,10,10]"}, "children": []} + ]}`, + "HomeScreen", + }, + { + "route marker repeated by a nested node", + `{"attributes": {"bounds": "[0,0,10,10]"}, "children": [ + {"attributes": {"resource-id": "HomeScreen", "bounds": "[0,0,10,10]"}, "children": [ + {"attributes": {"resource-id": "HomeScreen", "bounds": "[0,0,10,5]"}, "children": []} + ]} + ]}`, + "HomeScreen", + }, + { + "cross-fade names no screen", + `{"attributes": {"bounds": "[0,0,10,10]"}, "children": [ + {"attributes": {"resource-id": "HomeScreen", "bounds": "[0,0,10,10]"}, "children": []}, + {"attributes": {"resource-id": "LedgerScreen", "bounds": "[0,0,10,10]"}, "children": []} + ]}`, + "", + }, + { + "no marker at all", + `{"attributes": {"bounds": "[0,0,10,10]"}, "children": []}`, + "", + }, + } + for _, testCase := range cases { + t.Run(testCase.name, func(t *testing.T) { + tree, err := Parse(testCase.tree) + if err != nil { + t.Fatalf("Parse: %v", err) + } + if got := tree.ScreenName(); got != testCase.want { + t.Errorf("ScreenName = %q, want %q", got, testCase.want) + } + }) + } +}