Files
cthulhu/internal/review/document_lists_test.go

144 lines
5.3 KiB
Go

// Copyright (c) 2026 Storm Dragon
// Authors: Storm Dragon, Angus Kola
// SPDX-License-Identifier: GPL-3.0-or-later
package review
import (
"context"
"testing"
"time"
"git.stormux.org/storm/cthulhu/internal/atspi"
)
func TestDocumentListPreservesCursorAndSelectsExactTarget(t *testing.T) {
tree, cursor := webCursor(t)
ctx := context.Background()
if _, err := cursor.SyncFocus(ctx, webID("/doc")); err != nil {
t.Fatal(err)
}
capturer := &readAllCapturer{tree: tree}
worker, _ := NewWorker(capturer, time.Second)
worker.SyncFocus(webID("/doc"))
before := cursor.diagnostic_state()
list, err := worker.listDocument(ctx, cursor, &documentListRequest{ctx: ctx, target: DocumentHeading})
if err != nil || len(list.Entries) != 2 {
t.Fatalf("heading list: %+v %v", list, err)
}
if after := cursor.diagnostic_state(); after != before {
t.Fatalf("opening moved cursor: %+v -> %+v", before, after)
}
result, cursor := worker.selectDocumentList(ctx, cursor, &documentListSelection{list: list, index: 1})
if result.Err != nil || !result.Moved || result.DocumentPosition.Object != list.Entries[1].Object {
t.Fatalf("selection: %+v", result)
}
list, err = worker.listDocument(ctx, cursor, &documentListRequest{ctx: ctx, target: DocumentHeading})
if err != nil || list.Selected != 1 {
t.Fatalf("did not preserve current row: %+v %v", list, err)
}
filtered, err := worker.listDocument(ctx, cursor, &documentListRequest{ctx: ctx, target: DocumentHeading, level: 2})
if err != nil || len(filtered.Entries) != 2 || filtered.Entries[0].Snapshot.HeadingLevel != 2 {
t.Fatalf("level filter: %+v %v", filtered, err)
}
}
func TestDocumentListRejectsChangedTargetsAndOwnership(t *testing.T) {
for _, change := range []string{"target removed", "target reused", "target hidden", "focus changed", "document reloaded"} {
t.Run(change, func(t *testing.T) {
tree, cursor := webCursor(t)
ctx := context.Background()
worker, _ := NewWorker(&readAllCapturer{tree: tree}, time.Second)
worker.SyncFocus(webID("/doc"))
list, err := worker.listDocument(ctx, cursor, &documentListRequest{ctx: ctx, target: DocumentHeading})
if err != nil {
t.Fatal(err)
}
switch change {
case "target removed":
tree.Invalidate(list.Entries[0].Object.ID())
case "target reused":
tree.Invalidate(list.Entries[0].Object.ID())
if err := tree.Replace(webID("/root"), webNodes()); err != nil {
t.Fatal(err)
}
case "target hidden":
nodes := webNodes()
nodes[3].Geometry = atspi.Geometry{VisibilityKnown: true}
if err := tree.Replace(webID("/root"), nodes); err != nil {
t.Fatal(err)
}
case "focus changed":
worker.SyncFocus(webID("/chrome"))
case "document reloaded":
worker.ResetDocument(webID("/doc"))
}
result, _ := worker.selectDocumentList(ctx, cursor, &documentListSelection{list: list, index: 0})
if result.Err == nil || result.Moved {
t.Fatalf("stale selection succeeded: %+v", result)
}
})
}
}
func TestDocumentListDescendantLabelsExcludeHiddenAndPasswordText(t *testing.T) {
node := func(path, role, text string, children ...string) atspi.TreeNode {
snapshot := atspi.Snapshot{BusName: webID(path).BusName, Path: path, Role: role, Text: text}
entry := atspi.TreeNode{Snapshot: snapshot}
for _, child := range children {
entry.Children = append(entry.Children, webID(child))
}
return entry
}
nodes := []atspi.TreeNode{
node("/doc", "document web", "", "/clickable"),
node("/clickable", "section", "", "/text", "/hidden", "/password"),
node("/text", "static", "Clickable text"),
node("/hidden", "static", "Hidden content"),
node("/password", "password text", "private fixture password"),
}
nodes[1].Snapshot.SupportsClick = true
nodes[3].Geometry = atspi.Geometry{VisibilityKnown: true}
tree, err := atspi.NewAccessibleTree(webID("/doc"), nodes)
if err != nil {
t.Fatal(err)
}
worker, _ := NewWorker(&readAllCapturer{tree: tree}, time.Second)
worker.SyncFocus(webID("/doc"))
ctx := context.Background()
list, err := worker.listDocument(ctx, nil, &documentListRequest{ctx: ctx, target: DocumentClickable})
if err != nil || len(list.Entries) != 1 || list.Entries[0].Label != "Clickable text" {
t.Fatalf("unsafe or missing descendant label: %+v %v", list, err)
}
}
func TestWorkerDocumentListUsesOrderedRequests(t *testing.T) {
tree, _ := webCursor(t)
worker, cancel := startWorker(t, &readAllCapturer{tree: tree}, time.Second)
defer cancel()
worker.SyncFocus(webID("/doc"))
ctx, stop := context.WithTimeout(context.Background(), time.Second)
defer stop()
list, err := worker.ListDocument(ctx, DocumentHeading, 0)
if err != nil {
t.Fatal(err)
}
if err := worker.SelectDocumentList(list, 1); err != nil {
t.Fatal(err)
}
result := waitResult(t, worker)
if result.Err != nil || !result.Moved || result.DocumentPosition.Object != list.Entries[1].Object {
t.Fatalf("queued selection: %+v", result)
}
}
func TestAdditionalDocumentListRoles(t *testing.T) {
for target, roles := range map[DocumentTarget][]string{DocumentAnnotation: {"content deletion", "content insertion", "mark"}, DocumentInternalFrame: {"internal frame"}, DocumentMath: {"math"}} {
for _, role := range roles {
if !documentTargetMatches(target, atspi.DocumentNode{Snapshot: atspi.Snapshot{Role: role}}, 0) {
t.Fatalf("missing %s", role)
}
}
}
}