Cthulhu is no longer a fork. Now it is a screen reader written in Golang.
This commit is contained in:
1 parent
a998fe67de
commit
069fc674dc
708 files changed
+112482
-532
No files matched your search
@@ -0,0 +1,193 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"context"
|
||||
"image"
|
||||
"image/color"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.stormux.org/storm/cthulhu/internal/atspi"
|
||||
"git.stormux.org/storm/cthulhu/internal/config"
|
||||
"git.stormux.org/storm/cthulhu/internal/input"
|
||||
"git.stormux.org/storm/cthulhu/internal/ocr"
|
||||
)
|
||||
|
||||
func TestOCRClipboardRefreshKeepsSnapshotAndCursor(t *testing.T) {
|
||||
session, keys, capture, engine, output := ocr_test_session(t)
|
||||
session.Handle(input.CommandEvent{Command: input.OCRClipboard, Timestamp: 100})
|
||||
ocr_test_receive(t, keys.calls)
|
||||
ocr_test_receive(t, capture.calls)
|
||||
ocr_test_receive(t, engine.calls)
|
||||
engine.results <- ocr.Result{Text: "one two", Words: []ocr.Word{{Text: "one", Span: ocr.Span{Start: 0, End: 3}}, {Text: "two", Span: ocr.Span{Start: 4, End: 7}}}}
|
||||
ocr_test_receive(t, keys.calls)
|
||||
ocr_test_receive(t, output.speech)
|
||||
ocr_test_receive(t, output.frames)
|
||||
session.Handle(input.CommandEvent{Command: input.OCRNextWord, Timestamp: 101})
|
||||
ocr_test_receive(t, output.speech)
|
||||
ocr_test_receive(t, output.frames)
|
||||
session.Handle(input.CommandEvent{Command: input.OCRRefresh, Timestamp: 102})
|
||||
ocr_test_receive(t, engine.calls)
|
||||
select {
|
||||
case <-capture.calls:
|
||||
t.Fatal("clipboard refresh fetched a changed clipboard")
|
||||
default:
|
||||
}
|
||||
engine.results <- ocr.Result{Text: "one three", Words: []ocr.Word{{Text: "one", Span: ocr.Span{Start: 0, End: 3}}, {Text: "three", Span: ocr.Span{Start: 4, End: 9}}}}
|
||||
if got := ocr_test_receive(t, output.speech); got.Text != "one three" {
|
||||
t.Fatal(got)
|
||||
}
|
||||
session.mu.Lock()
|
||||
defer session.mu.Unlock()
|
||||
if session.cursor == nil || session.cursor.Offset() != 4 || session.cursor.Text() != "one three" || session.refreshFailed {
|
||||
t.Fatal("refresh lost cursor or snapshot")
|
||||
}
|
||||
}
|
||||
|
||||
func TestOCRRefreshRetainsUnchangedSuffixAcrossInsertion(t *testing.T) {
|
||||
if got := remapOCROffset("é one", "new é one", 2); got != 6 {
|
||||
t.Fatalf("cursor moved to inserted text: %d", got)
|
||||
}
|
||||
if got := remapOCROffset("é one", "é new one", 2); got != 6 {
|
||||
t.Fatalf("cursor missed original suffix: %d", got)
|
||||
}
|
||||
if got := remapOCROffset("old text", "old", 8); got != 3 {
|
||||
t.Fatalf("deleted suffix was not clamped: %d", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOCRStaleActivationCannotRetireReplacement(t *testing.T) {
|
||||
session, keys, _, engine, _ := ocr_test_session(t)
|
||||
defer func() { engine.results <- ocr.Result{}; engine.results <- ocr.Result{} }()
|
||||
session.Handle(input.CommandEvent{Command: input.OCRWindow, Timestamp: 100})
|
||||
ocr_test_receive(t, keys.calls)
|
||||
session.mu.Lock()
|
||||
old := session.job
|
||||
session.mu.Unlock()
|
||||
session.Handle(input.CommandEvent{Command: input.OCRDesktop, Timestamp: 101})
|
||||
session.mu.Lock()
|
||||
current := session.job.generation
|
||||
session.mu.Unlock()
|
||||
session.activate(ocrActivateRequest{job: old})
|
||||
session.mu.Lock()
|
||||
defer session.mu.Unlock()
|
||||
if session.cancel == nil || session.job.generation != current {
|
||||
t.Fatal("late activation retired replacement job")
|
||||
}
|
||||
}
|
||||
|
||||
func TestOCRAutoRefreshWaitsForEachJob(t *testing.T) {
|
||||
session, keys, capture, engine, output := ocr_test_session(t)
|
||||
live := &liveSettings{}
|
||||
settings := config.DefaultSettings()
|
||||
settings.OCR.AutoRefresh = true
|
||||
settings.OCR.RefreshInterval = 500
|
||||
live.Store(settings)
|
||||
session.live = live
|
||||
session.Handle(input.CommandEvent{Command: input.OCRWindow, Timestamp: 100})
|
||||
ocr_test_receive(t, keys.calls)
|
||||
ocr_test_receive(t, capture.calls)
|
||||
ocr_test_receive(t, engine.calls)
|
||||
engine.results <- ocr.Result{Text: "ready"}
|
||||
ocr_test_receive(t, keys.calls)
|
||||
ocr_test_receive(t, output.speech)
|
||||
ocr_test_receive(t, output.frames)
|
||||
ocr_test_receive(t, capture.calls)
|
||||
ocr_test_receive(t, engine.calls)
|
||||
select {
|
||||
case <-capture.calls:
|
||||
t.Fatal("automatic OCR jobs overlapped")
|
||||
case <-time.After(600 * time.Millisecond):
|
||||
}
|
||||
engine.results <- ocr.Result{Text: "ready"}
|
||||
ocr_test_receive(t, output.frames)
|
||||
ocr_test_receive(t, capture.calls)
|
||||
ocr_test_receive(t, engine.calls)
|
||||
}
|
||||
|
||||
type ocrTestClicker struct{ points chan image.Point }
|
||||
|
||||
func (clicker *ocrTestClicker) ClickOCR(ctx context.Context, _ uint64, _ image.Rectangle, point image.Point) error {
|
||||
clicker.points <- point
|
||||
return ctx.Err()
|
||||
}
|
||||
|
||||
type ocrTestConfirmer struct {
|
||||
answer bool
|
||||
called chan string
|
||||
}
|
||||
|
||||
func (confirm *ocrTestConfirmer) Confirm(_ context.Context, word string) (bool, error) {
|
||||
confirm.called <- word
|
||||
return confirm.answer, nil
|
||||
}
|
||||
|
||||
func TestOCRActivationChecksFreshPixelsBeforeClick(t *testing.T) {
|
||||
for _, test := range []struct {
|
||||
name string
|
||||
command input.Command
|
||||
changed bool
|
||||
}{
|
||||
{"object/unchanged", input.OCRObject, false},
|
||||
{"object/changed", input.OCRObject, true},
|
||||
{"inaccessible-window/unchanged", input.OCRWindow, false},
|
||||
{"inaccessible-window/changed", input.OCRWindow, true},
|
||||
} {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
session, keys, capture, engine, output := ocr_test_session(t)
|
||||
if test.command == input.OCRWindow {
|
||||
session.focus, session.window = atspi.ObjectID{}, atspi.ObjectID{}
|
||||
session.geometry, session.interest = nil, nil
|
||||
}
|
||||
clicker := &ocrTestClicker{points: make(chan image.Point, 1)}
|
||||
confirmer := &ocrTestConfirmer{answer: true, called: make(chan string, 1)}
|
||||
session.clicker, session.confirmer = clicker, confirmer
|
||||
paint := color.RGBA{R: 20, A: 255}
|
||||
capture.produce = func(ctx context.Context, request ocr.CaptureRequest) (ocr.Capture, error) {
|
||||
pixels := image.NewRGBA(image.Rect(0, 0, 100, 50))
|
||||
for index := range pixels.Pix {
|
||||
pixels.Pix[index] = 20
|
||||
}
|
||||
pixels.SetRGBA(0, 0, paint)
|
||||
bounds := image.Rect(10, 20, 110, 70)
|
||||
return ocr.Capture{CaptureHeader: ocr.CaptureHeader{Request: request, RootBounds: image.Rect(0, 0, 200, 200), SourceBounds: bounds, ScreenBounds: bounds, SourceWindow: request.Foreground}, Pixels: pixels,
|
||||
Transform: ocr.Transform{ImageContent: image.Rect(0, 0, 100, 50), ScreenBounds: bounds, HasScreenMapping: true}}, ctx.Err()
|
||||
}
|
||||
session.Handle(input.CommandEvent{Command: test.command, Timestamp: 100})
|
||||
ocr_test_receive(t, keys.calls)
|
||||
ocr_test_receive(t, capture.calls)
|
||||
ocr_test_receive(t, engine.calls)
|
||||
engine.results <- ocr.Result{Text: "go", Width: 100, Height: 50, Words: []ocr.Word{{Text: "go", Bounds: image.Rect(10, 10, 30, 20), Span: ocr.Span{Start: 0, End: 2}}}}
|
||||
ocr_test_receive(t, keys.calls)
|
||||
ocr_test_receive(t, output.speech)
|
||||
ocr_test_receive(t, output.frames)
|
||||
if test.changed {
|
||||
paint = color.RGBA{R: 30, A: 255}
|
||||
}
|
||||
session.Handle(input.CommandEvent{Command: input.OCRActivate, Timestamp: 101})
|
||||
if got := ocr_test_receive(t, confirmer.called); got != "go" {
|
||||
t.Fatal(got)
|
||||
}
|
||||
ocr_test_receive(t, capture.calls)
|
||||
if test.changed {
|
||||
if got := ocr_test_receive(t, output.speech); got.Text != "OCR source changed; refresh before activation" {
|
||||
t.Fatal(got)
|
||||
}
|
||||
select {
|
||||
case <-clicker.points:
|
||||
t.Fatal("clicked changed image")
|
||||
default:
|
||||
}
|
||||
} else {
|
||||
select {
|
||||
case point := <-clicker.points:
|
||||
if point != image.Pt(29, 34) {
|
||||
t.Fatal(point)
|
||||
}
|
||||
case <-time.After(3 * time.Second):
|
||||
t.Fatal("valid word was not clicked")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user