100 lines
3.4 KiB
Go
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)
|
|
}
|
|
}
|