From abb4890e8ae2991384bfbd79d7349e4f64fa4788 Mon Sep 17 00:00:00 2001 From: Angus Kola Date: Sat, 7 Nov 2026 22:08:48 -0500 Subject: [PATCH] Announce popup state for buttons and links --- internal/presentation/control_values.go | 6 ++++++ internal/presentation/control_values_test.go | 17 +++++++++++++++++ internal/presentation/document_line_test.go | 7 +++++++ 3 files changed, 30 insertions(+) diff --git a/internal/presentation/control_values.go b/internal/presentation/control_values.go index c2fb542..423320d 100644 --- a/internal/presentation/control_values.go +++ b/internal/presentation/control_values.go @@ -124,6 +124,12 @@ func controlStates(snapshot atspi.Snapshot) []string { states = append(states, "collapsed") } } + if snapshot.HasPopup { + switch snapshot.Role { + case "button", "push button", "toggle button", "link": + states = append(states, "has popup") + } + } if snapshot.Selected && presentsSelectedState(snapshot.Role) { states = append(states, "selected") } diff --git a/internal/presentation/control_values_test.go b/internal/presentation/control_values_test.go index 0a219f5..91a17ee 100644 --- a/internal/presentation/control_values_test.go +++ b/internal/presentation/control_values_test.go @@ -209,3 +209,20 @@ func TestSliderFocusUsesCurrentValueInsteadOfStaleText(t *testing.T) { }) } } + +func TestPopupControlPresentation(t *testing.T) { + for _, role := range []string{"button", "push button", "toggle button", "link", "combo box", "text"} { + t.Run(role, func(t *testing.T) { + plain := atspi.Snapshot{Name: "Options", Role: role} + popup := plain + popup.HasPopup = true + want := FocusText(plain) + if role == "button" || role == "push button" || role == "toggle button" || role == "link" { + want += ", has popup" + } + if got := FocusText(popup); got != want { + t.Fatalf("popup focus = %q, want %q", got, want) + } + }) + } +} diff --git a/internal/presentation/document_line_test.go b/internal/presentation/document_line_test.go index 50f79c2..b0c0f34 100644 --- a/internal/presentation/document_line_test.go +++ b/internal/presentation/document_line_test.go @@ -86,3 +86,10 @@ func TestDocumentLineSoundPolicy(t *testing.T) { t.Fatalf("speech-only text = %q", got) } } + +func TestDocumentLinePopupCue(t *testing.T) { + line := atspi.DocumentLine{Contents: []atspi.DocumentContent{{Snapshot: atspi.Snapshot{Role: "button", HasPopup: true}, Text: "Options"}}} + if got := DocumentLineText(line); got != "Options, button, has popup" { + t.Fatalf("popup line = %q", got) + } +}