172 lines
7.4 KiB
Go
172 lines
7.4 KiB
Go
package review
|
|
|
|
import (
|
|
"context"
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"git.stormux.org/storm/cthulhu/internal/atspi"
|
|
)
|
|
|
|
type currentLineCapturer struct {
|
|
*readAllCapturer
|
|
content atspi.DocumentContent
|
|
reads int
|
|
}
|
|
|
|
func (capturer *currentLineCapturer) ReadFocusedLine(context.Context, atspi.ObjectID) (atspi.DocumentContent, error) {
|
|
capturer.reads++
|
|
return capturer.content, nil
|
|
}
|
|
|
|
func TestCurrentLineUsesNativeFocusOutsideBrowse(t *testing.T) {
|
|
source := atspi.Snapshot{BusName: webID("/entry").BusName, Path: "/entry", Role: "text"}
|
|
capturer := ¤tLineCapturer{content: atspi.DocumentContent{Text: "Native caret line", Snapshot: source,
|
|
SourceContext: []atspi.Snapshot{source, {Role: "frame"}, {Role: "application"}}}}
|
|
worker, cancel := startWorker(t, capturer, time.Second)
|
|
defer cancel()
|
|
worker.SyncFocus(webID("/entry"))
|
|
for _, style := range []FlatSpeech{FlatSpeak, FlatSpell, FlatPhonetic} {
|
|
if err := worker.ReadCurrentLine(false, style); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
result := waitResult(t, worker)
|
|
if !result.CurrentLine || result.CurrentLineBrowse || result.FlatSpeech != style || result.Err != nil || result.DocumentCaretText != "Native caret line" {
|
|
t.Fatalf("native current line: %+v", result)
|
|
}
|
|
if !reflect.DeepEqual(result.DocumentCaretContext, capturer.content.SourceContext) {
|
|
t.Fatal("native current line lost its owned privacy context")
|
|
}
|
|
}
|
|
if capturer.reads != 3 {
|
|
t.Fatal("native line was cached")
|
|
}
|
|
}
|
|
|
|
func TestCurrentLineNativeMissingContextPreservesLocalText(t *testing.T) {
|
|
capturer := ¤tLineCapturer{content: atspi.DocumentContent{Text: "Local line"}}
|
|
worker, _ := NewWorker(capturer, time.Second)
|
|
worker.SyncFocus(webID("/entry"))
|
|
result := worker.read_current_line(t.Context(), nil, false)
|
|
if result.Err != nil || result.DocumentCaretText != "Local line" || len(result.DocumentCaretContext) != 0 {
|
|
t.Fatalf("missing privacy metadata changed local reading: %+v", result)
|
|
}
|
|
}
|
|
|
|
func TestCurrentLineReadsWholeBrowseLineWithoutMoving(t *testing.T) {
|
|
reader := caretTextReader{webID("/text"): "First line\nSecond café 猫\nThird", webID("/next"): "Next"}
|
|
cursor := newCaretTextCursor(t, reader)
|
|
capturer := &readAllCapturer{tree: cursor.tree, caretTextReader: reader}
|
|
worker, _ := NewWorker(capturer, time.Second)
|
|
worker.SyncFocus(webID("/text"))
|
|
for range 15 {
|
|
if _, err := cursor.MoveCaret(context.Background(), reader, CaretNextCharacter); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
before := append([]documentPlace(nil), cursor.places...)
|
|
result := worker.read_current_line(context.Background(), cursor, true)
|
|
if result.Err != nil || strings.TrimSpace(result.DocumentCaretText) != "Second café 猫" {
|
|
t.Fatalf("whole browse line from middle: %+v", result)
|
|
}
|
|
owner, err := cursor.tree.DocumentContext(result.DocumentPosition.Object)
|
|
if err != nil || len(result.DocumentCaretContext) == 0 || !reflect.DeepEqual(result.DocumentCaretContext, owner.Ancestors) {
|
|
t.Fatal("current-line report lost its captured source ancestry", err)
|
|
}
|
|
if !reflect.DeepEqual(cursor.places, before) || capturer.nativePlacements != 0 {
|
|
t.Fatal("report changed browse or native caret")
|
|
}
|
|
}
|
|
|
|
func TestCurrentLineJoinsBrowseFragmentsWithoutMoving(t *testing.T) {
|
|
cursor, reader := fragment_cursor(t, "paragraph")
|
|
if _, err := cursor.MoveCaret(context.Background(), reader, CaretNextWord); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
capturer := &readAllCapturer{tree: cursor.tree, caretTextReader: reader.caretTextReader}
|
|
// Retain the fixture's geometry so the report can collect both fragments.
|
|
source := ¤tLineFragmentCapturer{readAllCapturer: capturer, fragmentTextReader: reader}
|
|
worker, _ := NewWorker(source, time.Second)
|
|
worker.SyncFocus(webID("/one"))
|
|
before := append([]documentPlace(nil), cursor.places...)
|
|
result := worker.read_current_line(context.Background(), cursor, true)
|
|
if result.Err != nil || result.DocumentCaretText != "One two." || !reflect.DeepEqual(cursor.places, before) {
|
|
t.Fatalf("fragmented current line: %+v", result)
|
|
}
|
|
if result.DocumentCaretLine == nil || len(result.DocumentCaretLine.SourceContexts) != 2 || len(result.DocumentCaretContext) == 0 {
|
|
t.Fatal("fragmented report lost source ancestry")
|
|
}
|
|
}
|
|
|
|
func TestCurrentLineReadsBrowseControlLabel(t *testing.T) {
|
|
doc := atspi.Snapshot{BusName: webID("/doc").BusName, Path: "/doc", Role: "document web"}
|
|
button := atspi.Snapshot{BusName: doc.BusName, Path: "/button", Role: "push button", Name: "Example button"}
|
|
tree, err := atspi.NewAccessibleTree(doc.ID(), []atspi.TreeNode{
|
|
{Snapshot: doc, Children: []atspi.ObjectID{button.ID()}}, {Snapshot: button},
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
cursor, _ := NewDocumentCursor(tree)
|
|
if _, err := cursor.SyncFocus(context.Background(), button.ID()); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
worker, _ := NewWorker(&readAllCapturer{tree: tree, caretTextReader: caretTextReader{}}, time.Second)
|
|
worker.SyncFocus(button.ID())
|
|
result := worker.read_current_line(context.Background(), cursor, true)
|
|
if result.Err != nil || result.DocumentCaretText != "Example button" {
|
|
t.Fatalf("browse control label: %+v", result)
|
|
}
|
|
}
|
|
|
|
func TestCurrentLineUsesRecapturedSourceContext(t *testing.T) {
|
|
text := caretTextReader{webID("/text"): "First\nSecond", webID("/next"): "Last"}
|
|
cursor := newCaretTextCursor(t, text)
|
|
fresh := newCaretTextCursor(t, text).tree
|
|
capturer := &sequenceReadAllCapturer{caretTextReader: text, trees: []*atspi.AccessibleTree{fresh},
|
|
invalidateTree: cursor.tree, staleAt: 2}
|
|
worker, _ := NewWorker(capturer, time.Second)
|
|
worker.SyncFocus(webID("/text"))
|
|
result := worker.read_current_line(t.Context(), cursor, true)
|
|
if result.Err != nil || strings.TrimSpace(result.DocumentCaretText) != "First" || capturer.index != 1 {
|
|
t.Fatalf("current line did not recover capture: %+v", result)
|
|
}
|
|
owner, err := fresh.DocumentContext(result.DocumentPosition.Object)
|
|
if err != nil || len(result.DocumentCaretContext) == 0 || !reflect.DeepEqual(result.DocumentCaretContext, owner.Ancestors) {
|
|
t.Fatal("report did not retain the replacement tree's source context", err)
|
|
}
|
|
}
|
|
|
|
type currentLineStaleContextCapturer struct{ *readAllCapturer }
|
|
|
|
func (capturer *currentLineStaleContextCapturer) ReadDocumentCaret(ctx context.Context, reference atspi.ObjectReference, offset int, unit atspi.DocumentCaretUnit) (atspi.DocumentCaretText, error) {
|
|
text, err := capturer.caretTextReader.ReadDocumentCaret(ctx, reference, offset, unit)
|
|
if unit == atspi.DocumentCaretLineRemainder {
|
|
capturer.tree.InvalidateSnapshot(webID("/doc"))
|
|
}
|
|
return text, err
|
|
}
|
|
|
|
func TestCurrentLinePreservesLocalTextWithStaleContext(t *testing.T) {
|
|
text := caretTextReader{webID("/text"): "Local line", webID("/next"): "Last"}
|
|
cursor := newCaretTextCursor(t, text)
|
|
capturer := ¤tLineStaleContextCapturer{&readAllCapturer{caretTextReader: text, tree: cursor.tree}}
|
|
worker, _ := NewWorker(capturer, time.Second)
|
|
worker.SyncFocus(webID("/text"))
|
|
result := worker.read_current_line(t.Context(), cursor, true)
|
|
if result.Err != nil || result.DocumentCaretText != "Local line" || len(result.DocumentCaretContext) != 0 {
|
|
t.Fatalf("stale ancestry must leave local reading available without export metadata: %+v", result)
|
|
}
|
|
}
|
|
|
|
type currentLineFragmentCapturer struct {
|
|
*readAllCapturer
|
|
fragmentTextReader
|
|
}
|
|
|
|
func (capturer *currentLineFragmentCapturer) ReadDocumentCaret(ctx context.Context, ref atspi.ObjectReference, offset int, unit atspi.DocumentCaretUnit) (atspi.DocumentCaretText, error) {
|
|
return capturer.fragmentTextReader.ReadDocumentCaret(ctx, ref, offset, unit)
|
|
}
|