Files
cthulhu/internal/review/document_sound_test.go

104 lines
3.8 KiB
Go

// Copyright (c) 2026 Storm Dragon
// Authors: Storm Dragon, Angus Kola
// SPDX-License-Identifier: GPL-3.0-or-later
package review
import (
"context"
"errors"
"reflect"
"testing"
"time"
"git.stormux.org/storm/cthulhu/internal/atspi"
)
type soundGeometryNavigator struct {
*fakeDocumentNavigator
geometry [2]atspi.SoundGeometry
err error
reads int
t *testing.T
}
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") {
navigator.t.Fatal("geometry refresh did not follow placement of the selected heading and link")
}
default:
navigator.t.Fatal("geometry read before scrolling the target")
}
return navigator.geometry, navigator.err
}
func TestDocumentSoundGeometryRefreshPreservesSemanticsAndPlacement(t *testing.T) {
oldGeometry := atspi.SoundGeometry{ObjectKnown: true, Object: atspi.Rectangle{X: 900, Y: 3000, Width: 50, Height: 15}}
freshGeometry := atspi.SoundGeometry{
ObjectKnown: true, ReferenceKnown: true,
Object: atspi.Rectangle{X: 100, Y: 300, Width: 50, Height: 15},
Reference: atspi.Rectangle{Width: 1000, Height: 600},
}
for _, name := range []string{"fresh", "unavailable", "refresh failed", "placement failed"} {
t.Run(name, func(t *testing.T) {
node := func(path, role string, children ...atspi.ObjectID) atspi.TreeNode {
snapshot := atspi.NewSnapshot(path, "Topic", role, "", "")
snapshot.BusName = webID(path).BusName
snapshot.SoundGeometry = oldGeometry
return atspi.TreeNode{Snapshot: snapshot, Children: children}
}
tree, err := atspi.NewAccessibleTree(webID("/doc"), []atspi.TreeNode{
node("/doc", "document web", webID("/heading")),
node("/heading", "heading", webID("/topic_link")),
node("/topic_link", "link"),
})
if err != nil {
t.Fatal(err)
}
cursor, _ := NewDocumentCursor(tree)
document, _ := tree.Root()
heading, _ := tree.Reference(webID("/heading"))
linkRef, _ := tree.Reference(webID("/topic_link"))
snapshot, _ := tree.Resolve(heading)
link, _ := tree.Resolve(linkRef)
navigator := &soundGeometryNavigator{fakeDocumentNavigator: newFakeDocumentNavigator(), t: t}
want := atspi.SoundGeometry{}
switch name {
case "fresh":
navigator.geometry = [2]atspi.SoundGeometry{freshGeometry, freshGeometry}
want = freshGeometry
case "refresh failed":
navigator.err = context.DeadlineExceeded
case "placement failed":
navigator.placementErr <- atspi.ErrStaleObject
want = oldGeometry
}
worker, _ := NewWorker(navigator, time.Second)
worker.SyncFocus(webID("/doc"))
result := worker.placeDocument(context.Background(), cursor, Result{
Moved: true, DocumentEpoch: worker.DocumentEpoch(), DocumentFocus: webID("/doc"),
DocumentPosition: DocumentPosition{Document: document, Object: heading, Snapshot: snapshot, LinkedHeading: &link},
})
if result.Err != nil || !errors.Is(result.DocumentSoundErr, navigator.err) {
t.Fatalf("sound refresh affected navigation: %+v", result)
}
if result.DocumentPosition.Snapshot.SoundGeometry != want || result.DocumentPosition.LinkedHeading.SoundGeometry != want {
t.Fatalf("geometry after %s = %+v", name, result.DocumentPosition)
}
if link.SoundGeometry != oldGeometry {
t.Fatal("refresh mutated the previously captured link snapshot")
}
cached, _ := tree.Resolve(heading)
if !reflect.DeepEqual(cached, snapshot) {
t.Fatal("refresh changed cached document semantics or geometry")
}
if name == "placement failed" && navigator.reads != 0 {
t.Fatal("failed caret placement still queried sound geometry")
}
})
}
}