Files

133 lines
4.4 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"
"testing"
"time"
"git.stormux.org/storm/cthulhu/internal/atspi"
)
func TestObjectInterestIsOrderedAndPreservesExplicitReview(t *testing.T) {
capturer := &fakeCapturer{calls: make(chan captureCall, 1)}
worker, cancel := startWorker(t, capturer, time.Second)
defer cancel()
defer worker.Stop(context.Background())
worker.SyncFocus(objectID("/first"))
ctx, stop := context.WithTimeout(context.Background(), time.Second)
defer stop()
got, err := worker.ObjectOfInterest(ctx)
if err != nil || got.Object != objectID("/first") {
t.Fatalf("focus interest: %+v %v", got, err)
}
worker.Move(NextSibling)
call := <-capturer.calls
replies := make(chan interestReply, 1)
go func() { interest, err := worker.ObjectOfInterest(ctx); replies <- interestReply{interest, err} }()
tree := newTree(t)
call.result <- captureResult{tree: tree}
if result := waitResult(t, worker); result.Err != nil {
t.Fatal(result.Err)
}
reply := <-replies
if reply.err != nil || reply.interest.Object != objectID("/second") {
t.Fatalf("explicit review snapped to focus: %+v", reply)
}
worker.SyncFocus(objectID("/grandchild"))
worker.SyncFocus(objectID("/first"))
got, err = worker.ObjectOfInterest(ctx)
if err != nil || got.Object != objectID("/first") {
t.Fatalf("away/back retained explicit review: %+v %v", got, err)
}
worker.SyncFocus(objectID("/grandchild"))
got, err = worker.ObjectOfInterest(ctx)
if err != nil || got.Object != objectID("/grandchild") {
t.Fatalf("changed focus: %+v %v", got, err)
}
worker.Move(Parent)
if result := waitResult(t, worker); result.Err != nil {
t.Fatal(result.Err)
}
tree.Invalidate(objectID("/second"))
if _, err = worker.ObjectOfInterest(ctx); err == nil {
t.Fatal("stale explicit interest fell back to focus")
}
worker.ClearFocus()
if _, err = worker.ObjectOfInterest(ctx); !errors.Is(err, ErrNoCurrent) {
t.Fatalf("clear focus: %v", err)
}
}
func TestFlatInterestUsesZoneAndDoesNotRecapture(t *testing.T) {
capturer := &fakeCapturer{calls: make(chan captureCall, 1)}
worker, cancel := startWorker(t, capturer, time.Second)
defer cancel()
defer worker.Stop(context.Background())
worker.SyncFocus(objectID("/b"))
worker.ReadFlat(Line, 0)
call := <-capturer.calls
tree := flatTree(t, flatNode("/a", "First", 0, 0), flatNode("/b", "Second", 0, 30))
call.result <- captureResult{tree: tree}
waitResult(t, worker)
worker.ReadFlat(Line, -1)
position := waitResult(t, worker).FlatPosition
ctx, cancelQuery := context.WithTimeout(context.Background(), time.Second)
defer cancelQuery()
got, err := worker.ObjectOfInterest(ctx)
if err != nil || got.Object != objectID("/a") || !got.HasZone || got.Zone != position.Bounds {
t.Fatalf("zone: %+v %v", got, err)
}
tree.InvalidateGeometry(objectID("/a"))
if _, err = worker.ObjectOfInterest(ctx); !errors.Is(err, atspi.ErrStaleGeometry) {
t.Fatalf("stale zone: %v", err)
}
select {
case <-capturer.calls:
t.Fatal("interest recaptured a tree")
default:
}
}
func TestInterestPrefersVirtualCaretThenExplicitReview(t *testing.T) {
ctx := context.Background()
reader := caretTextReader{webID("/text"): "First line", webID("/next"): "Last line"}
document := newCaretTextCursor(t, reader)
if _, err := document.SyncFocus(ctx, webID("/text")); err != nil {
t.Fatal(err)
}
if _, err := document.MoveCaret(ctx, reader, CaretNextLine); err != nil {
t.Fatal(err)
}
got, err := resolve_interest(ctx, webID("/text"), 0, nil, nil, nil, document)
if err != nil || got.Object != webID("/next") {
t.Fatalf("virtual caret: %+v %v", got, err)
}
cursor, _ := NewCursor(newTree(t))
cursor.SyncFocus(objectID("/first"))
cursor.Move(NextSibling)
got, err = resolve_interest(ctx, webID("/text"), 1, cursor, nil, nil, document)
if err != nil || got.Object != objectID("/second") {
t.Fatalf("explicit precedence: %+v %v", got, err)
}
}
func TestInterestCancelledAndStopped(t *testing.T) {
capturer := &fakeCapturer{calls: make(chan captureCall, 1)}
worker, cancel := startWorker(t, capturer, time.Second)
ctx, stop := context.WithCancel(context.Background())
stop()
if _, err := worker.ObjectOfInterest(ctx); !errors.Is(err, context.Canceled) {
t.Fatalf("cancel: %v", err)
}
cancel()
worker.Stop(context.Background())
if _, err := worker.ObjectOfInterest(context.Background()); !errors.Is(err, ErrNoCurrent) {
t.Fatalf("stop: %v", err)
}
}