Fixed some element types being spoken even though sound only is selected.
This commit is contained in:
1 parent
6ee0454fbe
commit
0d76e44f9a
5 files changed
+53
-6
No files matched your search
@@ -22,6 +22,7 @@ func radio_sound_fixture(t *testing.T) (*eventPresenter, *fakeSpeechQueue, func(
|
|||||||
state := &eventLoopState{}
|
state := &eventLoopState{}
|
||||||
state.presentationOptions = presentation.DefaultOptions()
|
state.presentationOptions = presentation.DefaultOptions()
|
||||||
state.presentationOptions.EnableSound = true
|
state.presentationOptions.EnableSound = true
|
||||||
|
state.presentationOptions.PlaySoundForRole = true
|
||||||
state.presentationOptions.PlaySoundForState = true
|
state.presentationOptions.PlaySoundForState = true
|
||||||
state.presentationOptions.RoleSoundPresentation = "sound_only"
|
state.presentationOptions.RoleSoundPresentation = "sound_only"
|
||||||
queue := &fakeSpeechQueue{spoken: make(chan string, 8), requests: make(chan speechRequest, 8)}
|
queue := &fakeSpeechQueue{spoken: make(chan string, 8), requests: make(chan speechRequest, 8)}
|
||||||
@@ -40,7 +41,7 @@ func TestRadioFocusSoundCoalescesSelectionWithoutDelayingLabel(t *testing.T) {
|
|||||||
option := atspi.Snapshot{BusName: ":1.7", Path: "/radio", Role: "radio button", Name: "Laptop", Mnemonic: "<Alt>l"}
|
option := atspi.Snapshot{BusName: ":1.7", Path: "/radio", Role: "radio button", Name: "Laptop", Mnemonic: "<Alt>l"}
|
||||||
present(atspi.NewEvent(atspi.FocusEvent, option))
|
present(atspi.NewEvent(atspi.FocusEvent, option))
|
||||||
request := <-queue.requests
|
request := <-queue.requests
|
||||||
if !strings.Contains(request.Text, "Laptop") || len(request.Sound) != 0 || presenter.state.pending.radioSound.ready == nil {
|
if !strings.Contains(request.Text, "Laptop") || strings.Contains(request.Text, "radio button") || len(request.Sound) != 0 || presenter.state.pending.radioSound.ready == nil {
|
||||||
t.Fatalf("focus label/sound = %+v", request)
|
t.Fatalf("focus label/sound = %+v", request)
|
||||||
}
|
}
|
||||||
// The old member loses selection before GTK checks the focused member.
|
// The old member loses selection before GTK checks the focused member.
|
||||||
|
|||||||
@@ -127,7 +127,10 @@ func FocusSpeechWithOptions(snapshot atspi.Snapshot, options Options) Speech {
|
|||||||
role = ""
|
role = ""
|
||||||
}
|
}
|
||||||
_, replaceRole := roleSoundCue(snapshot, options)
|
_, replaceRole := roleSoundCue(snapshot, options)
|
||||||
if options.RoleSoundPresentation == "sound_only" && replaceRole {
|
_, replaceState := stateSoundCue(snapshot, options)
|
||||||
|
// A control-specific state cue also identifies its role. Keep the role
|
||||||
|
// spoken when role sounds are disabled or no replacement cue is available.
|
||||||
|
if options.RoleSoundPresentation == "sound_only" && (replaceRole || options.PlaySoundForRole && replaceState) {
|
||||||
role = ""
|
role = ""
|
||||||
}
|
}
|
||||||
if options.SpeechVerbosityLevel != 0 && snapshot.ChildCountKnown && snapshot.ChildCount > 0 {
|
if options.SpeechVerbosityLevel != 0 && snapshot.ChildCountKnown && snapshot.ChildCount > 0 {
|
||||||
@@ -147,7 +150,6 @@ func FocusSpeechWithOptions(snapshot atspi.Snapshot, options Options) Speech {
|
|||||||
}
|
}
|
||||||
fields = append(fields, snapshot.Name, role)
|
fields = append(fields, snapshot.Name, role)
|
||||||
states := controlStates(snapshot)
|
states := controlStates(snapshot)
|
||||||
_, replaceState := stateSoundCue(snapshot, options)
|
|
||||||
if options.RoleSoundPresentation == "sound_only" && replaceState {
|
if options.RoleSoundPresentation == "sound_only" && replaceState {
|
||||||
states = removeReplacedState(states)
|
states = removeReplacedState(states)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
package presentation
|
package presentation
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
@@ -209,7 +210,7 @@ func TestElementPresentationStrategiesKeepRoleAndStateIndependent(t *testing.T)
|
|||||||
|
|
||||||
checkbox := atspi.Snapshot{Name: "Accept", Role: "check box", Checked: true, Required: true, Invalid: true}
|
checkbox := atspi.Snapshot{Name: "Accept", Role: "check box", Checked: true, Required: true, Invalid: true}
|
||||||
options := Options{EnableSound: true, PlaySoundForRole: true, PlaySoundForState: true, RoleSoundPresentation: "sound_only"}
|
options := Options{EnableSound: true, PlaySoundForRole: true, PlaySoundForState: true, RoleSoundPresentation: "sound_only"}
|
||||||
if got := FocusTextWithOptions(checkbox, options); got != "Accept, check box, required, invalid entry" {
|
if got := FocusTextWithOptions(checkbox, options); got != "Accept, required, invalid entry" {
|
||||||
t.Fatalf("sound-only checkbox text = %q", got)
|
t.Fatalf("sound-only checkbox text = %q", got)
|
||||||
}
|
}
|
||||||
unsupported := atspi.Snapshot{Name: "Document", Role: "text frame", Expandable: true, Expanded: true}
|
unsupported := atspi.Snapshot{Name: "Document", Role: "text frame", Expandable: true, Expanded: true}
|
||||||
@@ -222,6 +223,47 @@ func TestElementPresentationStrategiesKeepRoleAndStateIndependent(t *testing.T)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestStateCueReplacesControlRoleInSoundOnlyFocus(t *testing.T) {
|
||||||
|
for _, role := range []string{"check box", "check menu item", "radio button", "radio menu item", "switch", "menu item"} {
|
||||||
|
for _, checked := range []bool{false, true} {
|
||||||
|
for _, strategy := range []string{"sound_only", "sound_and_speech", "speech_only"} {
|
||||||
|
t.Run(fmt.Sprintf("%s/%t/%s", role, checked, strategy), func(t *testing.T) {
|
||||||
|
snapshot := atspi.Snapshot{Name: "Option", Role: role, Checked: checked, Checkable: role == "menu item"}
|
||||||
|
options := Options{EnableSound: true, PlaySoundForRole: true, PlaySoundForState: true, RoleSoundPresentation: strategy}
|
||||||
|
want := FocusText(snapshot)
|
||||||
|
if strategy == "sound_only" {
|
||||||
|
want = "Option"
|
||||||
|
}
|
||||||
|
if got := FocusTextWithOptions(snapshot, options); got != want {
|
||||||
|
t.Fatalf("focus speech = %q, want %q", got, want)
|
||||||
|
}
|
||||||
|
if strategy != "sound_only" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, disabled := range []string{"sound", "state", "unavailable"} {
|
||||||
|
fallback := options
|
||||||
|
switch disabled {
|
||||||
|
case "sound":
|
||||||
|
fallback.EnableSound = false
|
||||||
|
case "state":
|
||||||
|
fallback.PlaySoundForState = false
|
||||||
|
case "unavailable":
|
||||||
|
fallback.SoundCueAvailable = func(string) bool { return false }
|
||||||
|
}
|
||||||
|
if got := FocusTextWithOptions(snapshot, fallback); got != FocusText(snapshot) {
|
||||||
|
t.Errorf("%s fallback = %q, want %q", disabled, got, FocusText(snapshot))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
options.PlaySoundForRole = false
|
||||||
|
if got, want := FocusTextWithOptions(snapshot, options), "Option, "+snapshot.ControlRole(); got != want {
|
||||||
|
t.Errorf("disabled role sound speech = %q, want %q", got, want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestStateChangeSoundOnlySuppressesOnlyReplacedState(t *testing.T) {
|
func TestStateChangeSoundOnlySuppressesOnlyReplacedState(t *testing.T) {
|
||||||
options := Options{EnableSound: true, PlaySoundForRole: true, PlaySoundForState: true, RoleSoundPresentation: "sound_only"}
|
options := Options{EnableSound: true, PlaySoundForRole: true, PlaySoundForState: true, RoleSoundPresentation: "sound_only"}
|
||||||
event := atspi.NewEvent("object:state-changed:checked", atspi.Snapshot{Role: "check box", Checked: true})
|
event := atspi.NewEvent("object:state-changed:checked", atspi.Snapshot{Role: "check box", Checked: true})
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ import "strings"
|
|||||||
|
|
||||||
const Name = "Cthulhu"
|
const Name = "Cthulhu"
|
||||||
|
|
||||||
const Version = "2026.10.25"
|
const Version = "2026.10.26"
|
||||||
const Channel = "testing"
|
const Channel = "testing"
|
||||||
|
|
||||||
var Build = Version
|
var Build = Version
|
||||||
|
|||||||
@@ -143,7 +143,9 @@ def check_radio_buttons(controls, tabs, server, readerLog):
|
|||||||
time.sleep(0.35)
|
time.sleep(0.35)
|
||||||
cues = radio_cues()[cueOffset:]
|
cues = radio_cues()[cueOffset:]
|
||||||
assert cues == ["radiobutton_checked"], (keyName, target, cues)
|
assert cues == ["radiobutton_checked"], (keyName, target, cues)
|
||||||
assert any(target in record["text"] for record in server.snapshot()[speechOffset:]), target + " label was lost"
|
spoken = [record["text"] for record in server.snapshot()[speechOffset:]]
|
||||||
|
assert any(target in text for text in spoken), target + " label was lost"
|
||||||
|
assert not any("radio button" in text for text in spoken), (target, spoken)
|
||||||
records = [json.loads(line) for line in readerLog.read_text().splitlines() if line.startswith("{")]
|
records = [json.loads(line) for line in readerLog.read_text().splitlines() if line.startswith("{")]
|
||||||
errors = [record for record in records if record.get("message") == "play sound"
|
errors = [record for record in records if record.get("message") == "play sound"
|
||||||
and record.get("cue", "").startswith("radiobutton_") and record.get("error") != "context canceled"]
|
and record.get("cue", "").startswith("radiobutton_") and record.get("error") != "context canceled"]
|
||||||
|
|||||||
Reference in new issue
Block a user