118 lines
3.5 KiB
Go
118 lines
3.5 KiB
Go
// Copyright (c) 2026 Storm Dragon
|
|
// Authors: Storm Dragon, Angus Kola
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
package app
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"os"
|
|
"os/exec"
|
|
"testing"
|
|
"time"
|
|
|
|
"git.stormux.org/storm/cthulhu/internal/atspi"
|
|
"git.stormux.org/storm/cthulhu/internal/review"
|
|
)
|
|
|
|
func TestNativeBrowserTooltipNavigation(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()
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
fmt.Fprint(w, `<!doctype html><title>Tooltip navigation regression</title><style>li {list-style: none}</style>
|
|
<ul><li><a id="project" href="#after" aria-describedby="tip"
|
|
onfocus="document.getElementById('tip').hidden=false"
|
|
onblur="document.getElementById('tip').hidden=true">Example project</a>
|
|
<span id="tip" role="tooltip" hidden><span>Example project</span></span>
|
|
<p id="after">After project</p></li></ul>`)
|
|
}))
|
|
defer server.Close()
|
|
adapter := atspi.New()
|
|
if err := adapter.Start(ctx); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer adapter.Stop(context.Background())
|
|
command := func(args ...string) {
|
|
t.Helper()
|
|
if output, err := exec.CommandContext(ctx, "xdotool", args...).CombinedOutput(); err != nil {
|
|
t.Fatalf("xdotool: %v: %s", err, output)
|
|
}
|
|
}
|
|
command("key", "--clearmodifiers", "ctrl+l")
|
|
command("type", "--clearmodifiers", "--delay", "0", server.URL)
|
|
command("key", "Return")
|
|
var document atspi.ObjectID
|
|
for !document.Valid() {
|
|
select {
|
|
case event := <-adapter.Events():
|
|
if event.Type == atspi.DocumentLoadCompleteEvent && event.Source.Name == "Tooltip navigation regression" {
|
|
document = event.Source.ID()
|
|
}
|
|
case <-ctx.Done():
|
|
t.Fatal(ctx.Err())
|
|
}
|
|
}
|
|
worker, err := review.NewWorker(adapter, 2*time.Second)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := worker.Start(ctx); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer worker.Stop(context.Background())
|
|
worker.SyncFocus(document)
|
|
move := func(command review.DocumentCaretCommand, want string) {
|
|
t.Helper()
|
|
if err := worker.MoveDocumentCaret(command); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
select {
|
|
case result := <-worker.Results():
|
|
if result.Err != nil || result.CaretErr != nil || !result.Moved || result.DocumentCaretText != want {
|
|
t.Fatalf("%s: text=%q want=%q err=%v caret=%v diagnostics=%+v", command,
|
|
result.DocumentCaretText, want, result.Err, result.CaretErr, result.DocumentDiagnostics)
|
|
}
|
|
case <-ctx.Done():
|
|
t.Fatal(ctx.Err())
|
|
}
|
|
}
|
|
move(review.CaretNextLine, "Example project")
|
|
// Capture after focus has exposed the tooltip. This proves the fixture
|
|
// supplies a real native tooltip subtree, rather than merely hidden text.
|
|
for {
|
|
tree, err := adapter.CaptureDocument(ctx, document)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
ref, _ := tree.Reference(document)
|
|
doc, err := tree.Document(ctx, ref)
|
|
found := false
|
|
if err == nil {
|
|
for _, node := range doc.Nodes {
|
|
if node.Snapshot.Role == "tool tip" && !node.Hidden && len(node.Children) > 0 {
|
|
found = true
|
|
}
|
|
}
|
|
}
|
|
if found {
|
|
break
|
|
}
|
|
select {
|
|
case <-adapter.Events():
|
|
case <-ctx.Done():
|
|
t.Fatal("visible native tooltip missing: ", ctx.Err())
|
|
}
|
|
}
|
|
move(review.CaretNextLine, "After project")
|
|
move(review.CaretPreviousLine, "Example project")
|
|
move(review.CaretLineEnd, "t")
|
|
move(review.CaretNextCharacter, "A")
|
|
move(review.CaretPreviousCharacter, "t")
|
|
}
|