Cthulhu is no longer a fork. Now it is a screen reader written in Golang.
This commit is contained in:
1 parent
a998fe67de
commit
069fc674dc
708 files changed
+112482
-532
No files matched your search
@@ -0,0 +1,136 @@
|
||||
// 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 (
|
||||
"errors"
|
||||
"math"
|
||||
"sync"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestRectangleIntersection(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
first, second, want Rectangle
|
||||
ok bool
|
||||
}{
|
||||
{"overlap", Rectangle{10, 20, 30, 40}, Rectangle{20, 30, 50, 60}, Rectangle{20, 30, 20, 30}, true},
|
||||
{"negative monitor", Rectangle{-100, -50, 90, 40}, Rectangle{-80, -30, 50, 50}, Rectangle{-80, -30, 50, 20}, true},
|
||||
{"touching edge", Rectangle{0, 0, 10, 10}, Rectangle{10, 0, 5, 5}, Rectangle{}, false},
|
||||
{"disjoint", Rectangle{0, 0, 10, 10}, Rectangle{20, 0, 5, 5}, Rectangle{}, false},
|
||||
{"empty", Rectangle{}, Rectangle{0, 0, 10, 10}, Rectangle{}, false},
|
||||
{"negative size", Rectangle{0, 0, -1, 10}, Rectangle{0, 0, 10, 10}, Rectangle{}, false},
|
||||
{"unavailable position", Rectangle{math.MinInt32, 0, 10, 10}, Rectangle{math.MinInt32, 0, 10, 10}, Rectangle{}, false},
|
||||
{"edge overflow", Rectangle{math.MaxInt32 - 5, 0, 10, 10}, Rectangle{math.MaxInt32 - 2, 1, 10, 10}, Rectangle{math.MaxInt32 - 2, 1, 7, 9}, true},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, ok := tt.first.Intersection(tt.second)
|
||||
if ok != tt.ok || got != tt.want {
|
||||
t.Fatalf("intersection = %v %v, want %v %v", got, ok, tt.want, tt.ok)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTreeGeometryInvalidationPreservesHierarchy(t *testing.T) {
|
||||
nodes := []TreeNode{
|
||||
testNode("/root", "Window", testID("/first"), testID("/second")),
|
||||
testNode("/first", "First"),
|
||||
testNode("/second", "Second", testID("/child")),
|
||||
testNode("/child", "Child"),
|
||||
}
|
||||
geometry := Geometry{Bounds: Rectangle{-10, 20, 30, 40}, BoundsKnown: true, VisibilityKnown: true, Visible: true, Showing: true}
|
||||
for i := range nodes {
|
||||
nodes[i].Geometry = geometry
|
||||
}
|
||||
tree, err := NewAccessibleTree(testID("/root"), nodes)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
second, _ := tree.Reference(testID("/second"))
|
||||
child, _, _ := tree.FirstChild(second)
|
||||
first, _ := tree.Reference(testID("/first"))
|
||||
if count := tree.InvalidateGeometry(second.ID()); count != 2 {
|
||||
t.Fatalf("invalidated %d", count)
|
||||
}
|
||||
if count := tree.InvalidateGeometry(second.ID()); count != 0 {
|
||||
t.Fatalf("repeat invalidated %d", count)
|
||||
}
|
||||
for _, ref := range []ObjectReference{second, child} {
|
||||
if _, err := tree.Geometry(ref); !errors.Is(err, ErrStaleGeometry) {
|
||||
t.Fatalf("geometry = %v", err)
|
||||
}
|
||||
if _, err := tree.Resolve(ref); err != nil {
|
||||
t.Fatalf("geometry invalidation destroyed identity: %v", err)
|
||||
}
|
||||
}
|
||||
if got, err := tree.Geometry(first); err != nil || got != geometry {
|
||||
t.Fatalf("unrelated geometry = %v %v", got, err)
|
||||
}
|
||||
if got, ok, err := tree.FirstChild(second); err != nil || !ok || got != child {
|
||||
t.Fatalf("child = %v %v %v", got, ok, err)
|
||||
}
|
||||
nodes[2].Geometry.Bounds.X = 25
|
||||
if err := tree.Replace(testID("/root"), nodes); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got, err := tree.Geometry(second); err != nil || got.Bounds.X != 25 {
|
||||
t.Fatalf("recapture = %v %v", got, err)
|
||||
}
|
||||
tree.Invalidate(second.ID())
|
||||
if _, err := tree.Geometry(child); !errors.Is(err, ErrStaleObject) {
|
||||
t.Fatalf("removed geometry = %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTreeRejectsMalformedGeometry(t *testing.T) {
|
||||
for _, geometry := range []Geometry{
|
||||
{Showing: true},
|
||||
{Bounds: Rectangle{0, 0, 10, 10}},
|
||||
{BoundsKnown: true},
|
||||
{Bounds: Rectangle{0, 0, 10, 10}, BoundsKnown: true, VisibilityKnown: true, Visible: true},
|
||||
} {
|
||||
node := testNode("/root", "Window")
|
||||
node.Geometry = geometry
|
||||
if _, err := NewAccessibleTree(testID("/root"), []TreeNode{node}); !errors.Is(err, ErrInvalidTree) {
|
||||
t.Fatalf("accepted malformed geometry %v: %v", geometry, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTreeGeometryConcurrentInvalidation(t *testing.T) {
|
||||
tree := testTree(t)
|
||||
ref, _ := tree.Reference(testID("/second"))
|
||||
var readers sync.WaitGroup
|
||||
for range 8 {
|
||||
readers.Add(1)
|
||||
go func() {
|
||||
defer readers.Done()
|
||||
for range 100 {
|
||||
if _, err := tree.Geometry(ref); err != nil && !errors.Is(err, ErrStaleGeometry) {
|
||||
t.Errorf("geometry: %v", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
tree.InvalidateGeometry(ref.ID())
|
||||
readers.Wait()
|
||||
}
|
||||
Reference in new issue
Block a user