426 lines
19 KiB
Go
426 lines
19 KiB
Go
package app
|
|
|
|
import (
|
|
"context"
|
|
"log/slog"
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
"unicode/utf8"
|
|
|
|
"git.stormux.org/storm/cthulhu/internal/atspi"
|
|
"git.stormux.org/storm/cthulhu/internal/braille"
|
|
"git.stormux.org/storm/cthulhu/internal/config"
|
|
"git.stormux.org/storm/cthulhu/internal/preferences"
|
|
"git.stormux.org/storm/cthulhu/internal/review"
|
|
)
|
|
|
|
type frameRecorder struct{ frames chan braille.Frame }
|
|
|
|
type flashRecorder struct{ messages []string }
|
|
|
|
func (*flashRecorder) EnqueueFrame(braille.Frame) {}
|
|
func (recorder *flashRecorder) Flash(message string, _ config.BrailleOptions) bool {
|
|
recorder.messages = append(recorder.messages, message)
|
|
return true
|
|
}
|
|
|
|
func TestSystemMessageFlashesOnBraille(t *testing.T) {
|
|
live := &liveSettings{}
|
|
live.Store(config.DefaultSettings())
|
|
braille := &flashRecorder{}
|
|
queue := outputQueue{speech: &remoteSpeechQueue{}, braille: braille, live: live}
|
|
queue.Enqueue(speechRequest{Text: "Copied", Role: "system", Cause: "clipboard"})
|
|
queue.Enqueue(speechRequest{Text: "Current line", Role: "system", Cause: "document-caret-line"})
|
|
if len(braille.messages) != 1 || braille.messages[0] != "Copied" {
|
|
t.Fatalf("flash messages = %v", braille.messages)
|
|
}
|
|
}
|
|
|
|
func TestBrailleOptionsChangePresentation(t *testing.T) {
|
|
options := config.DefaultSettings().Braille
|
|
options.DisableEOL = true
|
|
options.WordWrap = true
|
|
options.SelectionIndicator = 0x40
|
|
entry := atspi.Snapshot{Role: "entry", Name: "Title", BrailleLine: "abcd", BrailleLineStart: 10,
|
|
BrailleCaret: 12, TextCaretKnown: true, SelectionStart: 11, SelectionEnd: 13}
|
|
frame := brailleFocusFrameWithOptions(entry, options)
|
|
if frame.Text != "Title abcd" || !frame.WordWrap || len(frame.Dots) != 10 || frame.Dots[7] != 0x40 || frame.Dots[8] != 0x40 {
|
|
t.Fatalf("entry frame = %+v", frame)
|
|
}
|
|
options.AbbreviateRoles = true
|
|
if got := brailleFocusFrameWithOptions(atspi.Snapshot{Role: "button", Name: "Save"}, options).Text; got != "Save btn" {
|
|
t.Fatalf("abbreviated button = %q", got)
|
|
}
|
|
options.Verbosity = 0
|
|
if got := brailleFocusFrameWithOptions(atspi.Snapshot{Role: "button", Name: "Save"}, options).Text; got != "Save" {
|
|
t.Fatalf("brief button = %q", got)
|
|
}
|
|
options.LinkIndicator = 0x80
|
|
link := brailleFocusFrameWithOptions(atspi.Snapshot{Role: "link", Name: "Help"}, options)
|
|
if len(link.Dots) != 4 || link.Dots[0] != 0x80 || link.Dots[3] != 0x80 {
|
|
t.Fatalf("link dots = %v", link.Dots)
|
|
}
|
|
}
|
|
|
|
func (recorder frameRecorder) EnqueueFrame(frame braille.Frame) { recorder.frames <- frame }
|
|
|
|
func TestBrailleFocusFrameTracksUnicodeCaret(t *testing.T) {
|
|
snapshot := atspi.NewSnapshot("/entry", "Message", "entry", "", "é猫x")
|
|
snapshot.TextCaretKnown = true
|
|
snapshot.BrailleLine = "é猫x"
|
|
snapshot.TextBeforeCaret = "é猫"
|
|
snapshot.BrailleCaret = 2
|
|
if got := brailleFocusFrame(snapshot); !reflect.DeepEqual(got, braille.Frame{Text: "Message é猫x $l", Cursor: 10}) {
|
|
t.Fatalf("braille frame = %+v", got)
|
|
}
|
|
snapshot.BrailleLine = "a é"
|
|
snapshot.TextBeforeCaret = "a "
|
|
snapshot.BrailleCaret = 3
|
|
if got := brailleFocusFrame(snapshot); !reflect.DeepEqual(got, braille.Frame{Text: "Message a é $l", Cursor: 11}) {
|
|
t.Fatalf("braille spacing/caret = %+v", got)
|
|
}
|
|
snapshot.BrailleLine = "é猫x"
|
|
snapshot.TextBeforeCaret = "other"
|
|
snapshot.BrailleCaret = 5
|
|
if got := brailleFocusFrame(snapshot); got.Cursor != -1 {
|
|
t.Fatalf("invalid caret should be hidden: %+v", got)
|
|
}
|
|
snapshot.Role = "password text"
|
|
if got := brailleFocusFrame(snapshot); got.Cursor != 0 || got.Text != "Message password text" {
|
|
t.Fatalf("protected text leaked into braille: %+v", got)
|
|
}
|
|
}
|
|
|
|
func TestBrailleFramesFollowAcceptedFocusAndCaretWithoutSpeech(t *testing.T) {
|
|
adapter := &fakeAdapter{events: make(chan atspi.Event, 8)}
|
|
frames := make(chan braille.Frame, 8)
|
|
queue := outputQueue{speech: discardSpeech{}, braille: frameRecorder{frames: frames}}
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
done := make(chan struct{})
|
|
go func() {
|
|
defer close(done)
|
|
_ = processEventsWithInput(ctx, slog.New(slog.DiscardHandler), queue, adapter, nil, nil, 0, nil, nil, nil, nil)
|
|
}()
|
|
defer func() { cancel(); <-done }()
|
|
entry := atspi.NewSnapshot("/entry", "Message", "entry", "", "é猫")
|
|
entry.Editable = true
|
|
entry.TextCaretKnown = true
|
|
entry.BrailleLine = "é猫"
|
|
adapter.events <- atspi.NewEvent(atspi.FocusEvent, entry)
|
|
wantFrame(t, frames, braille.Frame{Text: "Message é猫 $l", Cursor: 8})
|
|
entry.TextBeforeCaret = "é"
|
|
entry.BrailleCaret = 1
|
|
adapter.events <- atspi.NewEvent(atspi.CaretEvent, entry)
|
|
wantFrame(t, frames, braille.Frame{Text: "Message é猫 $l", Cursor: 9})
|
|
entry.Text = "é猫x"
|
|
entry.BrailleLine = "é猫x"
|
|
entry.TextBeforeCaret = "é猫x"
|
|
entry.BrailleCaret = 3
|
|
adapter.events <- atspi.NewEventWithPayload(atspi.TextInsertedEvent, entry, "x")
|
|
wantFrame(t, frames, braille.Frame{Text: "Message é猫x $l", Cursor: 11})
|
|
// State snapshots may omit text capture. They must retain the displayed
|
|
// line while updating its state suffix.
|
|
entry.TextCaretKnown = false
|
|
entry.BrailleLine = ""
|
|
entry.Required = true
|
|
adapter.events <- atspi.NewEvent("object:state-changed:required", entry)
|
|
wantFrame(t, frames, braille.Frame{Text: "Message é猫x $l required", Cursor: 11})
|
|
check := atspi.NewSnapshot("/check", "Save", "check box", "", "")
|
|
adapter.events <- atspi.NewEvent(atspi.FocusEvent, check)
|
|
wantFrame(t, frames, braille.Frame{Text: "< > Save check box", Cursor: 0})
|
|
check.Checked = true
|
|
adapter.events <- atspi.NewEvent("object:state-changed:checked", check)
|
|
wantFrame(t, frames, braille.Frame{Text: "<x> Save check box", Cursor: 0})
|
|
}
|
|
|
|
func wantFrame(t *testing.T, frames <-chan braille.Frame, want braille.Frame) {
|
|
t.Helper()
|
|
select {
|
|
case got := <-frames:
|
|
if !reflect.DeepEqual(got, want) {
|
|
t.Fatalf("braille frame = %+v, want %+v", got, want)
|
|
}
|
|
case <-time.After(time.Second):
|
|
t.Fatalf("timed out waiting for braille frame %+v", want)
|
|
}
|
|
}
|
|
|
|
func TestBraillePythonControlFormats(t *testing.T) {
|
|
for _, tc := range []struct {
|
|
name string
|
|
snapshot atspi.Snapshot
|
|
text string
|
|
cursor int
|
|
}{
|
|
{"unchecked", atspi.Snapshot{Name: "Save", Role: "check box"}, "< > Save check box", 0},
|
|
{"checked", atspi.Snapshot{Name: "Save", Role: "check box", Checked: true}, "<x> Save check box", 0},
|
|
{"mixed", atspi.Snapshot{Name: "Save", Role: "check box", Indeterminate: true}, "<-> Save check box", 0},
|
|
{"radio", atspi.Snapshot{Name: "First", Role: "radio button", Checked: true}, "&=y First radio button", 0},
|
|
{"toggle", atspi.Snapshot{Name: "Bold", Role: "toggle button", Checked: true}, "&=y Bold toggle button", 0},
|
|
{"button", atspi.Snapshot{Name: "More", Role: "push button", Expandable: true}, "More collapsed push button", 0},
|
|
{"menu", atspi.Snapshot{Name: "Save", Role: "menu item", SensitiveKnown: true}, "Save grayed", 0},
|
|
{"unchecked generic menu", atspi.Snapshot{Name: "Updates", Role: "menu item", Checkable: true}, "< > Updates check menu item", 0},
|
|
{"checked generic menu", atspi.Snapshot{Name: "Updates", Role: "menu item", Checked: true}, "<x> Updates check menu item", 0},
|
|
{"mixed generic menu", atspi.Snapshot{Name: "Updates", Role: "menu item", Indeterminate: true}, "<-> Updates check menu item", 0},
|
|
{"combo", atspi.Snapshot{Name: "Color", Role: "combo box", ValueText: "Blue"}, "Color Blue combo box", 6},
|
|
{"slider", atspi.Snapshot{Name: "Volume", Role: "slider", ValueKnown: true, CurrentValue: 1.25, MinimumIncrement: 0.01}, "Volume 1.25 slider", 0},
|
|
{"progress", atspi.Snapshot{Name: "Copy", Role: "progress bar", ValueKnown: true, CurrentValue: 25, MaximumValue: 100}, "Copy 25% progress bar", 0},
|
|
{"progress range", atspi.Snapshot{Name: "Copy", Role: "progress bar", ValueKnown: true, MinimumValue: 10, CurrentValue: 20, MaximumValue: 30}, "Copy 50% progress bar", 0},
|
|
{"tree", atspi.Snapshot{Name: "Files", Role: "tree item", Expandable: true, Expanded: true}, "Files expanded", 0},
|
|
} {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
frame := brailleFocusFrame(tc.snapshot)
|
|
if frame.Text != tc.text || frame.Cursor != tc.cursor {
|
|
t.Fatalf("got %+v, want %q cursor %d", frame, tc.text, tc.cursor)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestBrailleTextRoutesExcludeLabelsAndStates(t *testing.T) {
|
|
s := atspi.Snapshot{BusName: ":1.42", Path: "/entry", Name: "Message", Role: "entry", Editable: true, TextCaretKnown: true,
|
|
BrailleLine: "é猫\n", BrailleLineStart: 20, BrailleCaret: 22, Invalid: true, Required: true, ReadOnly: true}
|
|
f := brailleFocusFrame(s)
|
|
if f.Text != "Message é猫 $l invalid required rdonly" || f.Cursor != 10 || len(f.Routes) != 1 {
|
|
t.Fatalf("frame %+v", f)
|
|
}
|
|
r := f.Routes[0]
|
|
if r.Start != 8 || r.End != 13 || r.SourceStart != 20 || r.TextLength != 2 || r.SourceText != "é猫\n" {
|
|
t.Fatalf("route %+v", r)
|
|
}
|
|
s.Role = "terminal"
|
|
f = brailleFocusFrame(s)
|
|
if f.Text != "é猫 " || f.Cursor != 2 {
|
|
t.Fatalf("terminal frame %+v", f)
|
|
}
|
|
s.BrailleCaret = 19
|
|
if f = brailleFocusFrame(s); f.Cursor != -1 {
|
|
t.Fatalf("caret outside captured suffix: %+v", f)
|
|
}
|
|
}
|
|
|
|
type tableBrailleCapturer struct{ *documentLineCapturer }
|
|
|
|
type browseBrailleCapturer struct {
|
|
*documentLineCapturer
|
|
text string
|
|
}
|
|
|
|
func (capturer *browseBrailleCapturer) ReadDocumentCaret(_ context.Context, _ atspi.ObjectReference, offset int, unit atspi.DocumentCaretUnit) (atspi.DocumentCaretText, error) {
|
|
characters := []rune(capturer.text)
|
|
if offset < 0 || offset >= len(characters) {
|
|
return atspi.DocumentCaretText{CharacterCount: len(characters)}, nil
|
|
}
|
|
start, end := offset, offset+1
|
|
if unit != atspi.DocumentCaretCharacter {
|
|
for start > 0 && characters[start-1] != '\n' {
|
|
start--
|
|
}
|
|
for end < len(characters) && characters[end-1] != '\n' {
|
|
end++
|
|
}
|
|
}
|
|
return atspi.DocumentCaretText{Start: start, End: end, Text: string(characters[start:end]), CharacterCount: len(characters)}, nil
|
|
}
|
|
|
|
func TestBrowseCaretUpdatesBrailleWithoutNativeCaretEvents(t *testing.T) {
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
capturer := &browseBrailleCapturer{documentLineCapturer: &documentLineCapturer{calls: make(chan documentLineCaptureCall, 2)}, text: "é猫 word\nSecond line"}
|
|
worker, err := review.NewWorker(capturer, time.Second)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
settings := config.DefaultSettings()
|
|
settings.EnableBraille = false
|
|
prefsWorker := newPreferencesWorker(slog.New(slog.DiscardHandler), settings, nil)
|
|
prefsWorker.braille = &recordingBrailleSettings{}
|
|
prefsWorker.documentReview = worker
|
|
values := preferences.FromSettings(settings)
|
|
values.EnableBraille = true
|
|
if response := prefsWorker.apply(ctx, preferences.Request{Values: values}); !response.Accepted {
|
|
t.Fatalf("enabling braille for the running session failed: %+v", response)
|
|
}
|
|
if err = worker.Start(ctx); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer worker.Stop(context.Background())
|
|
adapter := &fakeAdapter{events: make(chan atspi.Event, 8)}
|
|
frames := make(chan braille.Frame, 16)
|
|
done := make(chan struct{})
|
|
go func() {
|
|
defer close(done)
|
|
processEventsWithInput(ctx, slog.New(slog.DiscardHandler), outputQueue{speech: discardSpeech{}, braille: frameRecorder{frames: frames}}, adapter, nil, worker, 0, nil, nil, nil, nil)
|
|
}()
|
|
defer func() { cancel(); <-done }()
|
|
doc := atspi.Snapshot{BusName: ":1.42", Path: "/doc", Role: "document web", StatesKnown: true}
|
|
text := atspi.Snapshot{BusName: ":1.42", Path: "/text", Role: "static", StatesKnown: true, CharacterCount: utf8.RuneCountInString(capturer.text)}
|
|
tree, err := atspi.NewAccessibleTree(doc.ID(), []atspi.TreeNode{{Snapshot: doc, Children: []atspi.ObjectID{text.ID()}}, {Snapshot: text}})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
adapter.events <- atspi.NewEvent(atspi.FocusEvent, doc)
|
|
wantFrame(t, frames, brailleFocusFrame(doc))
|
|
for index, step := range []struct {
|
|
command review.DocumentCaretCommand
|
|
line string
|
|
cursor int
|
|
}{
|
|
{review.CaretDocumentStart, "é猫 word", 0},
|
|
{review.CaretNextCharacter, "é猫 word", 1},
|
|
{review.CaretLineEnd, "é猫 word", 8},
|
|
{review.CaretNextLine, "Second line", 0},
|
|
{review.CaretPreviousLine, "é猫 word", 0},
|
|
{review.CaretDocumentEnd, "Second line", 10},
|
|
} {
|
|
if err = worker.MoveDocumentCaret(step.command); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if index == 0 {
|
|
select {
|
|
case call := <-capturer.calls:
|
|
call.result <- tree
|
|
case <-time.After(time.Second):
|
|
t.Fatal("document capture missing")
|
|
}
|
|
}
|
|
select {
|
|
case frame := <-frames:
|
|
if strings.TrimRight(frame.Text, " ") != step.line || frame.Cursor != step.cursor || len(frame.Routes) != 1 || frame.Document == nil || frame.Routes[0].Path != text.Path {
|
|
t.Fatalf("command %v: frame %+v, want %q cursor %d with document routing", step.command, frame, step.line, step.cursor)
|
|
}
|
|
case <-time.After(time.Second):
|
|
t.Fatalf("command %v did not update braille", step.command)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestBrowseBrailleUsesVirtualOffsetAndAncestorIndicators(t *testing.T) {
|
|
options := config.DefaultSettings().Braille
|
|
options.LinkIndicator = 0x80
|
|
options.WordWrap = true
|
|
result := review.Result{DocumentPosition: review.DocumentPosition{Offset: 12, Snapshot: atspi.Snapshot{Role: "static"}},
|
|
DocumentBrailleLine: &atspi.DocumentCaretText{Start: 10, Text: "é猫 word\n"},
|
|
DocumentCaretContext: []atspi.Snapshot{{Role: "link"}, {Role: "heading", HeadingLevel: 2}}}
|
|
frame := brailleDocumentFrame(result, options)
|
|
if frame.Text != "é猫 word h2" || frame.Cursor != 2 || !frame.WordWrap || len(frame.Routes) != 0 || len(frame.Dots) != 11 || frame.Dots[0] != 0x80 || frame.Dots[8] != 0 {
|
|
t.Fatalf("browse frame = %+v", frame)
|
|
}
|
|
result.DocumentPosition.Snapshot = atspi.Snapshot{Role: "password text", Name: "Secret"}
|
|
frame = brailleDocumentFrame(result, options)
|
|
if frame.Text != "Secret password text" || len(frame.Routes) != 0 {
|
|
t.Fatalf("protected document text leaked: %+v", frame)
|
|
}
|
|
}
|
|
|
|
func TestBrowseBrailleNamedLinkUsesLabelCursorWithoutNativeRoutes(t *testing.T) {
|
|
result := review.Result{DocumentPosition: review.DocumentPosition{Offset: 4,
|
|
Snapshot: atspi.Snapshot{Role: "link", ExplicitName: true, Name: "É猫 Info", CharacterCount: 1}}}
|
|
frame := brailleDocumentFrame(result, config.DefaultSettings().Braille)
|
|
if !strings.HasPrefix(frame.Text, "É猫 Info") || frame.Cursor != 4 || len(frame.Routes) != 0 {
|
|
t.Fatalf("named link lost label cursor or exposed synthetic native routes: %+v", frame)
|
|
}
|
|
}
|
|
|
|
func TestBrowseBrailleFragmentLineKeepsNativeCursor(t *testing.T) {
|
|
first := atspi.NewSnapshot("/first", "", "static", "", "")
|
|
first.BusName = ":1.999"
|
|
second := atspi.NewSnapshot("/second", "", "static", "", "")
|
|
second.BusName = first.BusName
|
|
result := review.Result{
|
|
DocumentPosition: review.DocumentPosition{Snapshot: second, Offset: 8},
|
|
DocumentCaretLine: &atspi.DocumentLine{Contents: []atspi.DocumentContent{
|
|
{Snapshot: first, Start: 0, End: 2, RawText: "é猫"},
|
|
{Snapshot: second, Start: 7, End: 11, RawText: "word", Separator: " "},
|
|
}},
|
|
DocumentCaretContext: []atspi.Snapshot{{Role: "heading", HeadingLevel: 2}},
|
|
}
|
|
frame := brailleDocumentFrame(result, config.DefaultSettings().Braille)
|
|
if frame.Text != "é猫 word h2" || frame.Cursor != 4 || len(frame.Routes) != 0 {
|
|
t.Fatalf("fragment frame = %+v", frame)
|
|
}
|
|
}
|
|
|
|
func TestLinkedHeadingBrailleKeepsHeadingAndLinkIndicators(t *testing.T) {
|
|
options := config.DefaultSettings().Braille
|
|
options.LinkIndicator = 0x80
|
|
heading := atspi.NewSnapshot("/heading", "Linked heading", "heading", "", "")
|
|
heading.HeadingLevel = 2
|
|
link := atspi.NewSnapshot("/link", "Linked heading", "link", "", "")
|
|
link.Visited = true
|
|
result := review.Result{DocumentPosition: review.DocumentPosition{Snapshot: heading, LinkedHeading: &link}}
|
|
frame := brailleDocumentFrame(result, options)
|
|
if !strings.Contains(frame.Text, "h2") || !strings.Contains(frame.Text, "vlnk") || len(frame.Dots) != utf8.RuneCountInString(frame.Text) || frame.Dots[0] != 0x80 || frame.Dots[len([]rune(heading.Name))] != 0 {
|
|
t.Fatalf("linked heading frame = %+v", frame)
|
|
}
|
|
result.DocumentBrailleLine = &atspi.DocumentCaretText{Text: "Linked heading"}
|
|
frame = brailleDocumentFrame(result, options)
|
|
if frame.Text != "Linked heading h2 vlnk" || frame.Dots[0] != 0x80 || frame.Dots[len([]rune(heading.Name))] != 0 {
|
|
t.Fatalf("linked heading line frame = %+v", frame)
|
|
}
|
|
}
|
|
|
|
func (*tableBrailleCapturer) ReadDocumentCaret(context.Context, atspi.ObjectReference, int, atspi.DocumentCaretUnit) (atspi.DocumentCaretText, error) {
|
|
return atspi.DocumentCaretText{}, nil
|
|
}
|
|
|
|
func TestTableNavigationUpdatesBrailleWithoutNativeCaretEvent(t *testing.T) {
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
capturer := &tableBrailleCapturer{&documentLineCapturer{calls: make(chan documentLineCaptureCall, 2)}}
|
|
worker, err := review.NewWorker(capturer, time.Second)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err = worker.Start(ctx); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer worker.Stop(context.Background())
|
|
adapter := &fakeAdapter{events: make(chan atspi.Event, 8)}
|
|
frames := make(chan braille.Frame, 8)
|
|
queue := outputQueue{speech: discardSpeech{}, braille: frameRecorder{frames: frames}}
|
|
done := make(chan struct{})
|
|
go func() {
|
|
defer close(done)
|
|
processEventsWithInput(ctx, slog.New(slog.DiscardHandler), queue, adapter, nil, worker, 0, nil, nil, nil, nil)
|
|
}()
|
|
defer func() { cancel(); <-done }()
|
|
doc := atspi.Snapshot{BusName: ":1.42", Path: "/doc", Role: "document web", StatesKnown: true}
|
|
table := atspi.Snapshot{BusName: ":1.42", Path: "/table", Role: "table", Name: "Probe", TableRowCount: 1, TableColumnCount: 1, StatesKnown: true}
|
|
cell := atspi.Snapshot{BusName: ":1.42", Path: "/cell", Role: "table cell", Name: "Cell contents", TablePositionKnown: true, TableRowSpan: 1, TableColumnSpan: 1, StatesKnown: true}
|
|
tree, err := atspi.NewAccessibleTree(doc.ID(), []atspi.TreeNode{{Snapshot: doc, Children: []atspi.ObjectID{table.ID()}}, {Snapshot: table, Children: []atspi.ObjectID{cell.ID()}}, {Snapshot: cell}})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
adapter.events <- atspi.NewEvent(atspi.FocusEvent, table)
|
|
wantFrame(t, frames, brailleFocusFrame(table))
|
|
if err = worker.MoveDocumentCaret(review.TableFirst); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
select {
|
|
case call := <-capturer.calls:
|
|
call.result <- tree
|
|
case <-time.After(time.Second):
|
|
t.Fatal("table capture missing")
|
|
}
|
|
wantFrame(t, frames, brailleFocusFrame(cell))
|
|
}
|
|
|
|
func TestBrowseBrailleRepeatedParentKeepsNativeCursor(t *testing.T) {
|
|
parent := atspi.NewSnapshot("/body", "", "paragraph", "", "")
|
|
parent.BusName = ":1.1"
|
|
child := atspi.NewSnapshot("/span", "", "section", "", "")
|
|
child.BusName = ":1.1"
|
|
result := review.Result{DocumentPosition: review.DocumentPosition{Snapshot: parent, Offset: 0}, DocumentCaretLine: &atspi.DocumentLine{Contents: []atspi.DocumentContent{
|
|
{Snapshot: parent, Start: 0, End: 7, RawText: "Before "},
|
|
{Snapshot: child, Start: 0, End: 9, RawText: "Read more"},
|
|
{Snapshot: parent, Start: 8, End: 15, RawText: " after."},
|
|
}}}
|
|
frame := brailleDocumentFrame(result, config.DefaultSettings().Braille)
|
|
if frame.Cursor != 0 {
|
|
t.Fatalf("text=%q cursor=%d; expected cursor=0", frame.Text, frame.Cursor)
|
|
}
|
|
}
|