Merge hunter terminal and keyboard improvements into testing

Preserve meaningful title status symbols during spinner suppression and keep testing version metadata.
This commit is contained in:
Storm Dragon committed 2026-11-04 12:25:16 -05:00
commit 4f9b89e38b
18 files changed
+1101 -16

No files matched your search

+147 -1
View File
@@ -10,6 +10,7 @@ import (
"log/slog"
"path/filepath"
"testing"
"time"
"git.stormux.org/storm/cthulhu/internal/atspi"
"git.stormux.org/storm/cthulhu/internal/config"
@@ -76,8 +77,96 @@ func TestAutomaticTitlePolicyCoalescesAndPreservesSpeech(t *testing.T) {
changed.Source.Name = "Loading 100%"
titles.accessible(changed)
titles.flush()
// A title restored within the repeat window is a repeat, not news;
// animated cycles pass through a blank frame every cycle and must
// not revive announcements. It announces again after the window.
if len(queue.requests) != 1 {
t.Fatal("restored title repeated within the repeat window")
}
}
func TestAutomaticTitleSuppressesAnimatedSpinnerCycle(t *testing.T) {
titles, queue, changed := titleControllerFixture(t)
// Terminal TUIs animate a spinner glyph in the title several times a
// second while the meaningful title text stays the same. The cycle
// includes a blank frame; it must not reset suppression.
for _, text := range []string{"⠋ Terminal", "⠙ Terminal", "⠹ Terminal", "", "⠋ Terminal", "Terminal", "", "⠙ Terminal"} {
changed.Source.Name = text
titles.accessible(changed)
titles.flush()
}
if len(queue.requests) != 1 || queue.requests[0].Text != "⠋ Terminal" {
t.Fatalf("spinner title requests = %+v, want only the first frame", queue.requests)
}
// A different title is not part of the animation and still announces.
changed.Source.Name = "Build finished"
titles.accessible(changed)
titles.flush()
if len(queue.requests) != 2 || queue.requests[1].Text != "Build finished" {
t.Fatalf("title after spinner = %+v", queue.requests)
}
// A leading count is meaningful: each value announces, a rapid repeat
// of an earlier value in the same cycle does not.
for _, text := range []string{"(2) Terminal", "(3) Terminal", "(2) Terminal"} {
changed.Source.Name = text
titles.accessible(changed)
titles.flush()
}
if len(queue.requests) != 4 {
t.Fatalf("counted title requests = %+v, want the cycle repeat suppressed", queue.requests)
}
}
func TestWindowReactivationDoesNotReviveSpinnerCycle(t *testing.T) {
titles, queue, changed := titleControllerFixture(t)
for _, text := range []string{"9 Terminal", "8 Terminal"} {
changed.Source.Name = text
titles.accessible(changed)
titles.flush()
}
if len(queue.requests) != 2 {
t.Fatal("restored title was not announced")
t.Fatalf("initial spinner titles = %+v", queue.requests)
}
// Leaving for another workspace deactivates (reset) and returning
// reactivates the same window. Neither may restart the cycle.
titles.reset()
titles.activated(changed)
for _, text := range []string{"9 Terminal", "8 Terminal"} {
changed.Source.Name = text
titles.accessible(changed)
titles.flush()
}
if len(queue.requests) != 2 {
t.Fatalf("spinner revived by workspace round-trip = %+v", queue.requests)
}
}
func TestNativeTitleSwitchSamplesDoNotReviveSpinnerCycle(t *testing.T) {
titles, queue, changed := titleControllerFixture(t)
source := &titleProbeSource{title: atspi.ForegroundTitle{Title: xorg.WindowTitle{Window: 10, TitleWindow: 10, Title: "Original"}}}
titles.source, titles.foreground = source, source
titles.nativeChanged()
titles.flush()
changed.Source.Name = "9 Terminal"
titles.accessible(changed)
titles.flush()
changed.Source.Name = "8 Terminal"
titles.accessible(changed)
titles.flush()
if len(queue.requests) != 2 {
t.Fatalf("initial spinner titles = %+v", queue.requests)
}
// A native sampler may re-report the same client as a switch on every
// sample. That must not wipe the animated-title repeat history, so the
// cycle returning to an earlier frame stays suppressed.
for _, text := range []string{"9 Terminal", "8 Terminal"} {
source.title.Epoch++
source.title.Title.Title = text
titles.nativeChanged()
titles.flush()
}
if len(queue.requests) != 2 {
t.Fatalf("spinner revived by native switch samples = %+v", queue.requests)
}
}
@@ -658,3 +747,60 @@ func TestWineReviewTitlesUseNativeApplicationPreferences(t *testing.T) {
t.Fatal("Wine title ignored its application preference")
}
}
func TestTitleRepeatHistoryIsScopedToWindow(t *testing.T) {
titles, queue, changed := titleControllerFixture(t)
changed.Source.Name = "Build finished"
titles.accessible(changed)
titles.flush()
other := ownerEvent(atspi.WindowActivatedEvent, ":1.51", "/window", "/window")
other.Source.Name, other.Source.Role = "Original", "frame"
titles.state.owner.activate(other)
titles.activated(other)
other.Type, other.Source.Name = atspi.AccessibleNameEvent, "Build finished"
titles.accessible(other)
titles.flush()
if len(queue.requests) != 2 {
t.Fatalf("another window's title was suppressed: %+v", queue.requests)
}
}
func TestTitleStatusSymbolsRemainMeaningful(t *testing.T) {
titles, queue, changed := titleControllerFixture(t)
for _, text := range []string{"✓ Build", "✗ Build", "! Build", "? Build"} {
changed.Source.Name = text
titles.accessible(changed)
titles.flush()
if len(queue.requests) == 0 || queue.requests[len(queue.requests)-1].Text != text {
t.Fatalf("meaningful title status %q was suppressed: %+v", text, queue.requests)
}
}
if len(queue.requests) != 4 {
t.Fatalf("status titles = %+v", queue.requests)
}
}
func TestTitleRepeatHistoryRefreshesAndExpires(t *testing.T) {
titles, _, changed := titleControllerFixture(t)
pending := pendingWindowTitle{window: changed.Source.ID(), text: "⠋ Terminal"}
start := time.Now()
if titles.repeatedWithinWindow(pending, start) {
t.Fatal("first title suppressed")
}
pending.text = "⠙ Terminal"
if !titles.repeatedWithinWindow(pending, start.Add(2*time.Second)) ||
!titles.repeatedWithinWindow(pending, start.Add(4*time.Second)) {
t.Fatal("active animation history expired")
}
if titles.repeatedWithinWindow(pending, start.Add(8*time.Second)) {
t.Fatal("inactive animation history did not expire")
}
pending.native = xorg.WindowTitle{Window: 10, TitleWindow: 11}
if titles.repeatedWithinWindow(pending, start.Add(8*time.Second)) {
t.Fatal("native window inherited AT-SPI-only history")
}
pending.native.TitleWindow = 12
if titles.repeatedWithinWindow(pending, start.Add(8*time.Second)) {
t.Fatal("another native child inherited title history")
}
}