Files
cthulhu/internal/app/current_line_test.go

136 lines
4.9 KiB
Go

package app
import (
"context"
"errors"
"log/slog"
"reflect"
"testing"
"time"
"git.stormux.org/storm/cthulhu/internal/atspi"
"git.stormux.org/storm/cthulhu/internal/input"
"git.stormux.org/storm/cthulhu/internal/review"
)
type currentLineAdapter struct{ *fakeAdapter }
func (*currentLineAdapter) ReadFocusedLine(_ context.Context, focus atspi.ObjectID) (atspi.DocumentContent, error) {
return atspi.DocumentContent{Text: "Complete caret line", Snapshot: atspi.Snapshot{BusName: focus.BusName, Path: focus.Path, Role: "entry"}}, nil
}
func TestCurrentLineEventLoopPreservesFocusedItem(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
adapter := &currentLineAdapter{&fakeAdapter{events: make(chan atspi.Event)}}
worker, _ := review.NewWorker(adapter, time.Second)
if err := worker.Start(ctx); err != nil {
t.Fatal(err)
}
queue := &fakeSpeechQueue{spoken: make(chan string, 8)}
requests := make(chan input.Command)
done := make(chan error, 1)
go func() {
done <- processEventsWithSettings(ctx, slog.New(slog.DiscardHandler), queue, adapter, requests, worker, 0, nil)
}()
t.Cleanup(func() {
cancel()
if err := worker.Stop(context.Background()); err != nil {
t.Error(err)
}
<-done
})
waitSpeech := func(want string) {
t.Helper()
select {
case got := <-queue.spoken:
if got != want {
t.Fatalf("speech %q, want %q", got, want)
}
case <-time.After(time.Second):
t.Fatalf("missing speech %q", want)
}
}
focus := atspi.NewSnapshot("/entry", "Original item", "entry", "", "")
focus.BusName = ":1.42"
adapter.events <- atspi.NewEvent(atspi.FocusEvent, focus)
waitSpeech("Original item, entry")
requests <- input.PresentCurrentLine
waitSpeech("Complete caret line")
requests <- input.WhereAmI
waitSpeech("Original item, entry")
}
func TestCurrentLinePresentationAndStaleResults(t *testing.T) {
focus := atspi.ObjectID{BusName: ":1.42", Path: "/entry"}
worker, _ := review.NewWorker(&fakeAdapter{}, time.Second)
worker.SyncFocus(focus)
queue := &fakeSpeechQueue{requests: make(chan speechRequest, 8), spoken: make(chan string, 8)}
handler := eventReviewHandler{objectReview: worker, speech: queue, focusGate: &focusGate{}}
for _, probe := range []struct {
text string
style review.FlatSpeech
err error
want string
spelling []string
}{
{"A book", review.FlatSpeak, nil, "A book", nil},
{"A", review.FlatSpell, nil, "A", []string{"A"}},
{"A", review.FlatPhonetic, nil, "A", []string{"alpha"}},
{"\n", review.FlatSpeak, nil, "blank", nil},
{"", review.FlatSpeak, nil, "blank", nil},
{"", review.FlatSpeak, errors.New("unavailable"), "Could not read current line.", nil},
} {
result := review.Result{CurrentLine: true, DocumentFocus: focus, DocumentCaretText: probe.text, FlatSpeech: probe.style, Err: probe.err}
handler.present_current_line(result)
got := <-queue.requests
if got.Text != probe.want || !reflect.DeepEqual(got.Spelling, probe.spelling) {
t.Fatalf("current line: %+v, want %q %v", got, probe.want, probe.spelling)
}
}
worker.ResetDocument(focus)
handler.present_current_line(review.Result{CurrentLine: true, DocumentFocus: focus})
if len(queue.requests) != 0 {
t.Fatal("stale report was spoken")
}
worker.SyncFocus(atspi.ObjectID{BusName: ":1.42", Path: "/other"})
handler.present_current_line(review.Result{CurrentLine: true, DocumentFocus: focus, DocumentEpoch: worker.DocumentEpoch()})
if len(queue.requests) != 0 {
t.Fatal("report from previous focus was spoken")
}
}
func TestCurrentLineSecureFocus(t *testing.T) {
for _, mode := range []string{"lock", "handoff", "foreground-lock", "foreground-handoff"} {
t.Run(mode, func(t *testing.T) {
focus := atspi.NewObjectID(":1.42", "/focus")
worker, _ := review.NewWorker(&fakeAdapter{}, time.Second)
worker.SyncFocus(focus)
queue := &fakeSpeechQueue{requests: make(chan speechRequest, 1), spoken: make(chan string, 1)}
handler := eventReviewHandler{state: &eventLoopState{}, objectReview: worker, speech: queue,
logger: slog.New(slog.DiscardHandler), focusGate: &focusGate{}}
handler.state.current = atspi.Snapshot{BusName: focus.BusName, Path: focus.Path, Role: "text"}
secure := func() {
if mode == "lock" || mode == "foreground-lock" {
handler.focusGate.SetLocked(true, nil, nil, nil, nil, &brailleWorker{})
} else {
handler.focusGate.SetSuspended(true)
}
}
if mode == "lock" || mode == "handoff" {
secure()
handler.syncForeground = func() { t.Fatal("secure command synchronized foreground") }
} else {
handler.syncForeground = secure
}
handler.current_line(input.PresentCurrentLine)
// An already completed read must also be rejected on secure input.
result := native_current_line_result()
result.DocumentFocus = focus
handler.present_current_line(result)
if len(queue.requests) != 0 {
t.Fatal("current line spoke during lock or handoff")
}
})
}
}