// 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" ) type documentBrailleReader struct { *fakeCapturer line atspi.DocumentCaretText err error calls int } func (reader *documentBrailleReader) ReadDocumentCaret(_ context.Context, _ atspi.ObjectReference, _ int, unit atspi.DocumentCaretUnit) (atspi.DocumentCaretText, error) { if unit != atspi.DocumentCaretBrailleLine { return atspi.DocumentCaretText{}, errors.New("expected raw braille line") } reader.calls++ return reader.line, reader.err } func TestDocumentBrailleReadsOnlyEnabledAcceptedNavigation(t *testing.T) { reader := &documentBrailleReader{fakeCapturer: &fakeCapturer{}, line: atspi.DocumentCaretText{Start: 10, End: 17, Text: "é猫 line"}} worker, err := NewWorker(reader, time.Second) if err != nil { t.Fatal(err) } cursor := newCaretTextCursor(t, caretTextReader{webID("/text"): "Text"}) position, err := cursor.tree.Reference(webID("/text")) if err != nil { t.Fatal(err) } worker.SyncFocus(webID("/text")) result := Result{Moved: true, DocumentCaretCommand: CaretNextCharacter, DocumentFocus: webID("/text"), DocumentPosition: DocumentPosition{Object: position, Offset: 12}} for _, test := range []struct { name string modify func(*Result) }{ {"disabled", func(*Result) { worker.SetDocumentBrailleEnabled(false) }}, {"boundary", func(r *Result) { r.Moved = false }}, {"navigation failure", func(r *Result) { r.Err = atspi.ErrStaleObject }}, {"placement failure", func(r *Result) { r.CaretErr = atspi.ErrStaleObject }}, {"retired page", func(r *Result) { r.DocumentEpoch++ }}, {"other focus", func(r *Result) { r.DocumentFocus = webID("/other") }}, {"table", func(r *Result) { r.DocumentCaretCommand = TableRight }}, {"password", func(r *Result) { r.DocumentPosition.Snapshot.Role = "password text" }}, {"explicit link", func(r *Result) { r.DocumentPosition.Snapshot = atspi.Snapshot{Role: "link", Name: "Example Game 2 copies", ExplicitName: true} }}, } { t.Run(test.name, func(t *testing.T) { worker.SetDocumentBrailleEnabled(true) r := result test.modify(&r) got := worker.readDocumentBraille(context.Background(), r) if got.DocumentBrailleLine != nil || reader.calls != 0 { t.Fatalf("unaccepted navigation read display text: %+v; calls=%d", got, reader.calls) } }) } worker.SetDocumentBrailleEnabled(true) got := worker.readDocumentBraille(context.Background(), result) if got.DocumentBrailleLine == nil || got.DocumentBrailleLine.Text != reader.line.Text || reader.calls != 1 { t.Fatalf("raw line not preserved: %+v; calls=%d", got, reader.calls) } reader.err = atspi.ErrStaleObject got = worker.readDocumentBraille(context.Background(), result) if got.DocumentBrailleLine != nil || got.Err != nil || !got.Moved || !errors.Is(got.DocumentBrailleErr, reader.err) { t.Fatalf("braille read failure changed successful navigation: %+v", got) } }