Files

100 lines
3.4 KiB
Go

// Cthulhu screen reader.
// Copyright (c) 2026 Storm Dragon
// Authors: Storm Dragon, Angus Kola
//
// This program is free software: you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by the Free
// Software Foundation, either version 3 of the License, or (at your option)
// any later version.
//
// This program is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
// more details.
//
// You should have received a copy of the GNU General Public License along with
// this program. If not, see <https://www.gnu.org/licenses/gpl-3.0.html>.
package atspi
import (
"context"
"errors"
"testing"
)
func reviewTree(t *testing.T) (*AccessibleTree, ObjectID) {
t.Helper()
id := func(path string) ObjectID { return NewObjectID(":1.42", path) }
node := func(path, role string, children ...ObjectID) TreeNode {
s := NewSnapshot(path, path, role, "", "")
s.BusName = ":1.42"
return TreeNode{Snapshot: s, Children: children}
}
tree, err := NewAccessibleTree(id("/desktop"), []TreeNode{
node("/desktop", "desktop frame", id("/window"), id("/other")),
node("/window", "frame", id("/button"), id("/dialog")),
node("/button", "push button"),
node("/dialog", "dialog", id("/entry")),
node("/entry", "entry"),
node("/other", "frame"),
})
if err != nil {
t.Fatal(err)
}
return tree, id("/button")
}
func TestReviewWindowScopeAndOwnership(t *testing.T) {
tree, focus := reviewTree(t)
snapshot, err := tree.ReviewWindow(context.Background(), focus)
nodes := snapshot.Nodes
if err != nil {
t.Fatal(err)
}
if len(nodes) != 4 || nodes[0].Snapshot.Path != "/window" || nodes[1].Snapshot.Path != "/button" || nodes[3].Snapshot.Path != "/entry" {
t.Fatalf("unexpected window: %#v", nodes)
}
nodes[0].Children[0] = ObjectID{}
focus.Path = "/entry"
snapshot, err = tree.ReviewWindow(context.Background(), focus)
nodes = snapshot.Nodes
if err != nil || len(nodes) != 2 || nodes[0].Snapshot.Path != "/dialog" {
t.Fatalf("dialog scope: %#v, %v", nodes, err)
}
focus.Path = "/button"
if _, err := tree.ReviewWindow(context.Background(), focus); err != nil {
t.Fatal("caller mutated tree:", err)
}
}
func TestReviewWindowRejectsStaleDataAndCancellation(t *testing.T) {
for _, tc := range []struct {
name string
invalidate func(*AccessibleTree, ObjectID)
want error
}{
{"children", func(tree *AccessibleTree, id ObjectID) { tree.InvalidateChildren(id) }, ErrIncompleteTree},
{"geometry", func(tree *AccessibleTree, id ObjectID) { tree.InvalidateGeometry(id) }, ErrStaleGeometry},
{"text", func(tree *AccessibleTree, id ObjectID) { tree.InvalidateText(id) }, ErrStaleText},
} {
t.Run(tc.name, func(t *testing.T) {
tree, focus := reviewTree(t)
tc.invalidate(tree, focus)
if snapshot, err := tree.ReviewWindow(context.Background(), focus); snapshot.Nodes != nil || !errors.Is(err, tc.want) {
t.Fatalf("nodes=%v err=%v", snapshot.Nodes, err)
}
})
}
tree, focus := reviewTree(t)
ctx, cancel := context.WithCancel(context.Background())
cancel()
if _, err := tree.ReviewWindow(ctx, focus); !errors.Is(err, context.Canceled) {
t.Fatal(err)
}
focus.Path = "/desktop"
if _, err := tree.ReviewWindow(context.Background(), focus); !errors.Is(err, ErrNoReviewWindow) {
t.Fatal(err)
}
}