Web fixes.

This commit is contained in:
ak47 committed 2026-11-04 21:22:51 -05:00
1 parent 914d174925
commit 453206b85b
11 files changed
+643 -25

No files matched your search

+84 -1
View File
@@ -7,11 +7,14 @@ package review
import (
"context"
"errors"
"fmt"
"math"
"reflect"
"testing"
"time"
"git.stormux.org/storm/cthulhu/internal/atspi"
"git.stormux.org/storm/cthulhu/internal/presentation"
)
type soundGeometryNavigator struct {
@@ -20,13 +23,24 @@ type soundGeometryNavigator struct {
err error
reads int
t *testing.T
link atspi.ObjectID
offset int
}
func (navigator *soundGeometryNavigator) SetDocumentCaretPosition(ctx context.Context, object atspi.ObjectReference, focus atspi.ObjectID, offset int) error {
navigator.offset = offset
return navigator.SetDocumentPosition(ctx, object, focus)
}
func (navigator *soundGeometryNavigator) CaptureDocumentSoundGeometry(_ context.Context, object, link atspi.ObjectReference) ([2]atspi.SoundGeometry, error) {
navigator.reads++
select {
case placed := <-navigator.calls:
if placed.position != object || link.ID() != webID("/topic_link") {
wantLink := navigator.link
if !wantLink.Valid() {
wantLink = webID("/topic_link")
}
if placed.position != object || link.ID() != wantLink {
navigator.t.Fatal("geometry refresh did not follow placement of the selected heading and link")
}
default:
@@ -35,6 +49,75 @@ func (navigator *soundGeometryNavigator) CaptureDocumentSoundGeometry(_ context.
return navigator.geometry, navigator.err
}
func TestCaretLinkSoundRefreshKeepsLeafAndOffsets(t *testing.T) {
old := atspi.SoundGeometry{ObjectKnown: true, ReferenceKnown: true,
Object: atspi.Rectangle{X: 900, Width: 80, Height: 20}, Reference: atspi.Rectangle{Width: 1000, Height: 600}}
for _, command := range []DocumentCaretCommand{CaretNextLine, CaretPreviousLine, CaretNextWord, CaretPreviousWord, CaretDocumentStart, CaretDocumentEnd} {
for _, state := range []string{"left", "center", "right", "disabled", "failure", "stale"} {
t.Run(fmt.Sprintf("%v/%s", command, state), func(t *testing.T) {
nodes := []atspi.TreeNode{
{Snapshot: atspi.Snapshot{BusName: webID("/doc").BusName, Path: "/doc", Role: "document web"}, Children: []atspi.ObjectID{webID("/link")}},
{Snapshot: atspi.Snapshot{BusName: webID("/link").BusName, Path: "/link", Role: "link", Name: "Wrapped link", Visited: true, SoundGeometry: old}, Children: []atspi.ObjectID{webID("/label")}},
{Snapshot: atspi.Snapshot{BusName: webID("/label").BusName, Path: "/label", Role: "static", Name: "Wrapped link", CharacterCount: 12, SoundGeometry: old}},
}
tree, err := atspi.NewAccessibleTree(webID("/doc"), nodes)
if err != nil {
t.Fatal(err)
}
cursor, _ := NewDocumentCursor(tree)
position, err := cursor.SyncFocus(context.Background(), webID("/label"))
if err != nil {
t.Fatal(err)
}
owner, _ := tree.DocumentContext(position.Object)
position.Offset = 8
fresh := old
fresh.Object.X = map[string]int32{"left": 0, "center": 460, "right": 920}[state]
navigator := &soundGeometryNavigator{fakeDocumentNavigator: newFakeDocumentNavigator(), t: t, link: webID("/link")}
want := fresh
switch state {
case "disabled":
want = atspi.SoundGeometry{}
case "failure":
want = atspi.SoundGeometry{}
navigator.err = context.DeadlineExceeded
case "stale":
want = atspi.SoundGeometry{}
tree.Invalidate(webID("/link"))
default:
navigator.geometry = [2]atspi.SoundGeometry{fresh, fresh}
}
worker, _ := NewWorker(navigator, time.Second)
worker.SyncFocus(webID("/doc"))
result := worker.placeDocumentCaret(context.Background(), cursor, Result{
Moved: true, DocumentEpoch: worker.DocumentEpoch(), DocumentFocus: webID("/doc"),
DocumentPosition: position, DocumentCaretCommand: command, DocumentCaretContext: owner.Ancestors,
})
if result.Err != nil || result.CaretErr != nil || result.DocumentPosition.Object != position.Object ||
result.DocumentPosition.Offset != 8 || navigator.offset != 8 || result.DocumentPosition.Snapshot.Role != "static" {
t.Fatalf("sound refresh changed caret ownership: %+v", result)
}
link := result.DocumentCaretContext[1]
if link.SoundGeometry != want || !link.Visited || owner.Ancestors[1].SoundGeometry != old {
t.Fatalf("ancestor geometry/semantics: %+v", link)
}
if state == "failure" && !errors.Is(result.DocumentSoundErr, context.DeadlineExceeded) || state == "stale" && result.DocumentSoundErr == nil {
t.Fatalf("missing refresh failure: %v", result.DocumentSoundErr)
}
options := presentation.Options{EnableSound: true, PlaySoundForRole: true, SpatializeObjectSounds: state != "disabled"}
cues := presentation.SoundCues(link, options)
if len(cues) != 1 || cues[0].Name != "link_visited" {
t.Fatalf("link cue = %+v", cues)
}
wantPan := map[string]float64{"left": -1, "right": 1}[state]
if math.Abs(cues[0].Pan-wantPan) > 0.001 {
t.Fatalf("pan = %g, want %g", cues[0].Pan, wantPan)
}
})
}
}
}
func TestDocumentSoundGeometryRefreshPreservesSemanticsAndPlacement(t *testing.T) {
oldGeometry := atspi.SoundGeometry{ObjectKnown: true, Object: atspi.Rectangle{X: 900, Y: 3000, Width: 50, Height: 15}}
freshGeometry := atspi.SoundGeometry{