Files
cthulhu/internal/atspi/text_bounds_test.go

120 lines
4.3 KiB
Go

// Copyright (c) 2026 Storm Dragon
// Authors: Storm Dragon, Angus Kola
// SPDX-License-Identifier: GPL-3.0-or-later
package atspi
import (
"context"
"errors"
"strings"
"testing"
)
type longReviewText struct {
runes []rune
queries int
largest int
}
func (source *longReviewText) count(context.Context) (int, error) { return len(source.runes), nil }
func (source *longReviewText) line(context.Context, int) (TextRange, error) {
return TextRange{Start: 0, End: len(source.runes), Text: string(source.runes)}, nil
}
func (source *longReviewText) bounds(ctx context.Context, start, end int) (Rectangle, error) {
return bounded_review_bounds(ctx, start, end, func(_ context.Context, first, last int) (Rectangle, error) {
source.queries++
source.largest = max(source.largest, last-first)
if last-first > 128 {
return Rectangle{}, errors.New("provider timed out measuring a long range")
}
return Rectangle{X: int32(first * 10), Width: int32((last - first) * 10), Height: 20}, nil
})
}
func TestCaptureVisibleTextBoundsLongHorizontalLine(t *testing.T) {
source := &longReviewText{runes: []rune(strings.Repeat("é猫", 1712))}
clip := Rectangle{X: 1000, Width: 300, Height: 20}
got, err := captureVisibleText(context.Background(), source, clip)
if err != nil || !got.Known || got.Limited {
t.Fatalf("long line: %+v, %v", got, err)
}
var spoken strings.Builder
for _, part := range got.Ranges {
if part.Start < 100 || part.End > 130 {
t.Fatalf("offscreen range retained: %+v", part)
}
spoken.WriteString(part.Text)
}
if spoken.String() != string(source.runes[100:130]) || source.largest > 128 || source.queries > 400 {
t.Fatalf("clipping: text_bytes=%d largest_query=%d queries=%d", spoken.Len(), source.largest, source.queries)
}
if !got.valid(Geometry{BoundsKnown: true, Bounds: clip}) {
t.Fatal("long clipped line violates owned text invariants")
}
}
func TestBoundedReviewBoundsIncludesInteriorAndEmptyChunks(t *testing.T) {
rectangles := []Rectangle{{X: 500, Width: 100, Height: 20}, {X: -50, Width: 300, Height: 20}, {}, {X: 400, Width: 50, Height: 20}}
queries := 0
got, err := bounded_review_bounds(context.Background(), 0, 512, func(_ context.Context, start, end int) (Rectangle, error) {
if end-start > 128 {
t.Fatalf("oversized query: %d", end-start)
}
queries++
return rectangles[start/128], nil
})
if err != nil || got != (Rectangle{X: -50, Width: 650, Height: 20}) || queries != 4 {
t.Fatalf("union lost interior bidi or empty chunks: %+v, %v, queries=%d", got, err, queries)
}
}
func TestBoundedReviewBoundsStopsOnFailureOrCancellation(t *testing.T) {
providerError := errors.New("provider unavailable")
if _, err := bounded_review_bounds(context.Background(), 0, 512, func(context.Context, int, int) (Rectangle, error) {
return Rectangle{}, providerError
}); !errors.Is(err, providerError) {
t.Fatalf("provider error hidden: %v", err)
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
queries := 0
_, err := bounded_review_bounds(ctx, 0, 512, func(context.Context, int, int) (Rectangle, error) {
queries++
cancel()
return Rectangle{Width: 100, Height: 20}, nil
})
if !errors.Is(err, context.Canceled) || queries != 1 {
t.Fatalf("cancellation: %v queries=%d", err, queries)
}
}
// Even a block entirely inside the viewport can contain zero-area glyphs.
// A horizontally clipped line must retain its existing per-glyph exclusions.
type zeroGlyphReviewText struct{ longReviewText }
func (source *zeroGlyphReviewText) bounds(ctx context.Context, start, end int) (Rectangle, error) {
if start == 5 && end == 6 {
return Rectangle{}, ctx.Err()
}
return source.longReviewText.bounds(ctx, start, end)
}
func TestCaptureVisibleTextClippedInteriorBlockExcludesZeroAreaGlyph(t *testing.T) {
source := &zeroGlyphReviewText{longReviewText: longReviewText{runes: []rune(strings.Repeat("x", 400))}}
got, err := captureVisibleText(context.Background(), source, Rectangle{Width: 2000, Height: 20})
if err != nil || !got.Known || got.Limited {
t.Fatalf("capture: %+v, %v", got, err)
}
characters := 0
for _, span := range got.Ranges {
if span.Start <= 5 && span.End > 5 {
t.Fatalf("zero-area glyph included: %+v", span)
}
characters += span.End - span.Start
}
if characters != 199 {
t.Fatalf("visible characters=%d, want 199", characters)
}
}