Files
cthulhu/internal/app/selection_native_test.go

111 lines
2.8 KiB
Go

// Copyright (c) 2026 Storm Dragon
// Authors: Storm Dragon, Angus Kola
// SPDX-License-Identifier: GPL-3.0-or-later
package app
import (
"context"
"os"
"os/exec"
"strings"
"testing"
"time"
"git.stormux.org/storm/cthulhu/internal/atspi"
)
func TestNativeBrowserSelection(t *testing.T) {
if os.Getenv("CTHULHU_NATIVE_BROWSER") != "1" {
t.Skip("requires private browser session")
}
ctx, cancel := context.WithTimeout(context.Background(), 25*time.Second)
defer cancel()
adapter := atspi.New()
if err := adapter.Start(ctx); err != nil {
t.Fatal(err)
}
defer adapter.Stop(context.Background())
keys := func(chord string) {
t.Helper()
if out, err := exec.CommandContext(ctx, "xdotool", "key", "--clearmodifiers", chord).CombinedOutput(); err != nil {
t.Fatalf("keys %s: %v %s", chord, err, out)
}
}
var entry atspi.Snapshot
for attempt := 0; attempt < 30 && !entry.ID().Valid(); attempt++ {
keys("Tab")
timer := time.NewTimer(250 * time.Millisecond)
waitFocus:
for {
select {
case event := <-adapter.Events():
if isFocusEvent(event.Type) && event.Source.Name == "Display name" && event.Source.Editable {
entry = event.Source
break waitFocus
}
case <-timer.C:
break waitFocus
case <-ctx.Done():
t.Fatal(ctx.Err())
}
}
timer.Stop()
}
if !entry.ID().Valid() {
t.Fatal("editable browser input not focused")
}
if out, err := exec.CommandContext(ctx, "xdotool", "type", "--clearmodifiers", "--delay", "20", "hello world").CombinedOutput(); err != nil {
t.Fatalf("type fixture text: %v %s", err, out)
}
waitSelection := func(want string) {
t.Helper()
deadline := time.NewTimer(3 * time.Second)
defer deadline.Stop()
for {
select {
case event := <-adapter.Events():
if event.Type == atspi.TextSelectionChangedEvent && event.Source.ID() == entry.ID() &&
strings.Contains(event.Source.SelectionText, want) {
if event.Source.SelectionEnd <= event.Source.SelectionStart {
t.Fatalf("selection range missing: %+v", event.Source)
}
return
}
case <-deadline.C:
t.Fatalf("no native selection containing %q", want)
case <-ctx.Done():
t.Fatal(ctx.Err())
}
}
}
keys("ctrl+a")
waitSelection("hello world")
keys("Right")
keys("shift+Left")
waitSelection("d")
keys("ctrl+shift+Left")
waitSelection("world")
waitMutation := func(eventType string) {
t.Helper()
deadline := time.NewTimer(3 * time.Second)
defer deadline.Stop()
for {
select {
case event := <-adapter.Events():
if event.Type == eventType && event.Source.ID() == entry.ID() {
return
}
case <-deadline.C:
t.Fatalf("no native %s in browser input", eventType)
case <-ctx.Done():
t.Fatal(ctx.Err())
}
}
}
keys("ctrl+x")
waitMutation(atspi.TextDeletedEvent)
keys("ctrl+v")
waitMutation(atspi.TextInsertedEvent)
}