136 lines
4.9 KiB
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 := ¤tLineAdapter{&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")
|
|
}
|
|
})
|
|
}
|
|
}
|