Files
cthulhu/internal/review/document_native_caret_test.go

116 lines
3.9 KiB
Go

// 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"
"unicode/utf8"
"git.stormux.org/storm/cthulhu/internal/atspi"
)
func mirroredCaretCursor(t *testing.T, text caretTextReader, multiple bool) *DocumentCursor {
t.Helper()
var nodes []atspi.TreeNode
for _, path := range []string{"/doc", "/paragraph", "/text", "/other"} {
snapshot := atspi.Snapshot{BusName: webID(path).BusName, Path: path, Role: "static", CharacterCount: utf8.RuneCountInString(text[webID(path)])}
node := atspi.TreeNode{Snapshot: snapshot}
if path == "/doc" {
node.Snapshot.Role = "document web"
node.Children = []atspi.ObjectID{webID("/paragraph")}
}
if path == "/paragraph" {
node.Snapshot.Role = "paragraph"
node.Children = []atspi.ObjectID{webID("/text")}
if multiple {
node.Children = append(node.Children, webID("/other"))
}
}
if path == "/other" && !multiple {
continue
}
nodes = append(nodes, node)
}
tree, err := atspi.NewAccessibleTree(webID("/doc"), nodes)
if err != nil {
t.Fatal(err)
}
cursor, err := NewDocumentCursor(tree)
if err != nil {
t.Fatal(err)
}
return cursor
}
func TestNativeCaretSelectionEndpointTransfersToTextChild(t *testing.T) {
text := caretTextReader{webID("/paragraph"): "When autocomplete", webID("/text"): "When autocomplete"}
cursor := mirroredCaretCursor(t, text, false)
for _, offset := range []int{6, 11, 17, 9, 0} {
if err := cursor.syncNativeCaret(context.Background(), text, webID("/paragraph"), offset); err != nil {
t.Fatal(err)
}
move, err := cursor.MoveCaret(context.Background(), text, CaretPreviousCharacter)
if err != nil {
t.Fatal(err)
}
if offset > 0 && (move.Position.Object.ID() != webID("/text") || move.Position.Offset != offset-1) {
t.Fatalf("offset %d: move = %+v", offset, move)
}
}
}
func TestNativeCaretRejectsAmbiguousOffsetDomains(t *testing.T) {
for _, test := range []struct {
name, child string
multiple bool
offset int
}{
{"different text same count", "other", false, 2},
{"different count", "shorter", false, 2},
{"embedded text", "hello", false, 2},
{"multiple children", "hello", true, 2},
{"invalid offset", "hello", false, 6},
} {
t.Run(test.name, func(t *testing.T) {
parent := "hello"
if test.name == "embedded text" {
parent = "\ufffc"
}
text := caretTextReader{webID("/paragraph"): parent, webID("/text"): test.child}
cursor := mirroredCaretCursor(t, text, test.multiple)
if err := cursor.syncNativeCaret(context.Background(), text, webID("/paragraph"), test.offset); !errors.Is(err, atspi.ErrNoDocumentCaret) {
t.Fatalf("sync = %v", err)
}
})
}
}
func TestWorkerNativeCaretHandoffPrecedesNextArrow(t *testing.T) {
text := caretTextReader{webID("/text"): "When autocomplete"}
navigator := &boundaryNavigator{fakeCapturer: &fakeCapturer{}, caretTextReader: text}
worker, err := NewWorker(navigator, time.Second)
if err != nil {
t.Fatal(err)
}
worker.SyncFocus(webID("/text"))
cursor := newCaretTextCursor(t, text)
syncResult, cursor := worker.syncDocumentCaret(context.Background(), cursor, request{nativeCaretFocus: webID("/text"), nativeCaretOffset: 11})
if syncResult.Err != nil || syncResult.DocumentPosition.Offset != 11 {
t.Fatalf("handoff = %+v", syncResult)
}
result, _ := worker.moveDocumentCaret(context.Background(), cursor, CaretNextCharacter, 0)
if result.Err != nil || result.DocumentPosition.Offset != 12 || result.DocumentCaretText != "p" || navigator.offset != 12 {
t.Fatalf("move after selection = %+v; native offset = %d", result, navigator.offset)
}
worker.SyncFocus(webID("/next"))
stale, _ := worker.syncDocumentCaret(context.Background(), cursor, request{nativeCaretFocus: webID("/text"), nativeCaretOffset: 2})
if !errors.Is(stale.Err, atspi.ErrStaleObject) {
t.Fatalf("stale sync = %+v", stale)
}
}