package ui import ( "strings" "testing" ) const sampleDump = ` ` func parseSample(t *testing.T) *Hierarchy { t.Helper() h, err := parseHierarchy([]byte(sampleDump)) if err != nil { t.Fatalf("parseHierarchy: %v", err) } return h } func TestBoundsAndCenter(t *testing.T) { n := &Node{Bounds: "[221,155][798,217]"} r, err := n.BoundsRect() if err != nil { t.Fatalf("BoundsRect: %v", err) } if r.X1 != 221 || r.Y1 != 155 || r.X2 != 798 || r.Y2 != 217 { t.Fatalf("unexpected rect %+v", r) } cx, cy, err := n.Center() if err != nil { t.Fatalf("Center: %v", err) } if cx != (221+798)/2 || cy != (155+217)/2 { t.Fatalf("unexpected centre (%d,%d)", cx, cy) } } func TestBoundsRejectsGarbage(t *testing.T) { for _, bad := range []string{"", "nope", "[1,2][3,4]junk", "[10,10][10,20]"} { if _, err := (&Node{Bounds: bad}).BoundsRect(); err == nil { t.Fatalf("expected error for bounds %q", bad) } } } func TestFindTextExactAndSubstring(t *testing.T) { h := parseSample(t) if got := len(h.FindText("Connected", true)); got != 1 { t.Fatalf("exact match: want 1, got %d", got) } // Substring is case-insensitive and matches more than one node. if got := len(h.FindText("network", false)); got != 2 { t.Fatalf("substring match: want 2 (parent + child), got %d", got) } // Content descriptions count as text. matches := h.FindText("Navigate up", true) if len(matches) != 1 || !matches[0].Clickable { t.Fatalf("content-desc match failed: %+v", matches) } if got := h.FindText("does not exist", false); got != nil { t.Fatalf("expected no matches, got %d", len(got)) } } func TestPickBestMatchPrefersClickable(t *testing.T) { h := parseSample(t) matches := h.FindText("network", false) if len(matches) != 2 { t.Fatalf("want 2 matches, got %d", len(matches)) } best, err := pickBestMatch(matches) if err != nil { t.Fatalf("pickBestMatch: %v", err) } if !best.Clickable { t.Fatalf("expected a clickable match, got %s", best.Label()) } } func TestPickBestMatchRejectsDisabledOnly(t *testing.T) { h := parseSample(t) matches := h.FindText("Disabled thing", true) if len(matches) != 1 { t.Fatalf("want 1 match, got %d", len(matches)) } if _, err := pickBestMatch(matches); err == nil { t.Fatal("expected an error when the only match is disabled") } } func TestEscapeInputText(t *testing.T) { cases := map[string]string{ "hello world": "'hello%sworld'", "Rolling Stones": "'Rolling%sStones'", "it's": `'it'\''s'`, } for in, want := range cases { if got := escapeInputText(in); got != want { t.Errorf("escapeInputText(%q) = %q, want %q", in, got, want) } } // A single quote must not be able to break out of the quoting. got := escapeInputText("a'; rm -rf /; 'b") if strings.Count(got, "'")%2 != 0 { t.Fatalf("unbalanced quotes in %q", got) } }