167 lines
4.6 KiB
Go
167 lines
4.6 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"
|
|
"git.stormux.org/storm/cthulhu/internal/input"
|
|
"git.stormux.org/storm/cthulhu/internal/platform/xorg"
|
|
"git.stormux.org/storm/cthulhu/internal/review"
|
|
)
|
|
|
|
func TestNativeBrowserTables(t *testing.T) {
|
|
if os.Getenv("CTHULHU_NATIVE_BROWSER") != "1" {
|
|
t.Skip("requires private browser session")
|
|
}
|
|
ctx, cancel := context.WithTimeout(context.Background(), 30*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 %v %s", err, out)
|
|
}
|
|
}
|
|
var focus atspi.Snapshot
|
|
for attempt := 0; attempt < 25 && focus.Name != "Browser foundation"; attempt++ {
|
|
keys("Tab")
|
|
timer := time.NewTimer(200 * time.Millisecond)
|
|
drain:
|
|
for {
|
|
select {
|
|
case event := <-adapter.Events():
|
|
if isFocusEvent(event.Type) {
|
|
focus = event.Source
|
|
}
|
|
if focus.Name == "Browser foundation" {
|
|
break drain
|
|
}
|
|
case <-timer.C:
|
|
break drain
|
|
case <-ctx.Done():
|
|
t.Fatal(ctx.Err())
|
|
}
|
|
}
|
|
timer.Stop()
|
|
}
|
|
if focus.Name != "Browser foundation" {
|
|
t.Fatal("fixture focus missing")
|
|
}
|
|
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(focus.ID())
|
|
commands := xorg.NewCommands([]string{"Insert", "KP_Insert"}, input.DesktopKeyboardLayout)
|
|
if err = commands.Start(ctx); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer commands.Stop(context.Background())
|
|
applied := func(enabled bool) {
|
|
t.Helper()
|
|
commands.SetBrowseNavigation(enabled)
|
|
deadline := time.Now().Add(time.Second)
|
|
for commands.BrowseNavigationEnabled() != enabled && time.Now().Before(deadline) {
|
|
time.Sleep(10 * time.Millisecond)
|
|
}
|
|
if commands.BrowseNavigationEnabled() != enabled {
|
|
t.Fatal("browse grabs did not change")
|
|
}
|
|
}
|
|
applied(true)
|
|
command := func(chord string) input.Command {
|
|
t.Helper()
|
|
keys(chord)
|
|
select {
|
|
case got := <-commands.Commands():
|
|
return got.Command
|
|
case err := <-commands.Failures():
|
|
t.Fatal(err)
|
|
case <-time.After(2 * time.Second):
|
|
t.Fatalf("no command for %s", chord)
|
|
}
|
|
return ""
|
|
}
|
|
// Mode changes replace native browse grabs between successive A presses.
|
|
if got := command("Insert+a"); got != input.ToggleDocumentMode {
|
|
t.Fatal(got)
|
|
}
|
|
applied(false)
|
|
if got := command("Insert+a"); got != input.StickyFocusMode {
|
|
t.Fatal(got)
|
|
}
|
|
if got := command("Insert+a"); got != input.StickyBrowseMode {
|
|
t.Fatal(got)
|
|
}
|
|
applied(true)
|
|
result := func() review.Result {
|
|
t.Helper()
|
|
select {
|
|
case got := <-worker.Results():
|
|
if got.Err != nil || got.CaretErr != nil {
|
|
t.Fatalf("navigation errors %v %v", got.Err, got.CaretErr)
|
|
}
|
|
return got
|
|
case <-ctx.Done():
|
|
t.Fatal(ctx.Err())
|
|
}
|
|
return review.Result{}
|
|
}
|
|
if got := command("t"); got != input.DocumentNextTable {
|
|
t.Fatal(got)
|
|
}
|
|
if err = worker.MoveDocument(review.DocumentTable, 0, false); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got := result(); got.DocumentPosition.Snapshot.Name != "Navigation probe" {
|
|
t.Fatalf("table %+v", got)
|
|
}
|
|
for _, step := range []struct {
|
|
key, text string
|
|
moved bool
|
|
}{
|
|
{"alt+shift+Home", "Item", true},
|
|
{"alt+shift+Right", "Count", true},
|
|
{"alt+shift+Down", "Five", true},
|
|
{"alt+shift+Down", "Spans 2 columns.", true},
|
|
{"alt+shift+Down", "blank Row 4, column 2.", true},
|
|
{"alt+shift+Down", "Bottom of column.", false},
|
|
{"alt+shift+Up", "Merged total", true},
|
|
{"alt+shift+Up", "Five", true},
|
|
{"alt+shift+End", "blank", true},
|
|
{"alt+shift+Left", "Last item", true},
|
|
} {
|
|
cmd := command(step.key)
|
|
if err = worker.MoveDocumentCaret(documentCaretCommand(cmd)); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
got := result()
|
|
if got.Moved != step.moved || !strings.Contains(got.DocumentCaretText, step.text) {
|
|
t.Fatalf("%s got %+v want %s", step.key, got, step.text)
|
|
}
|
|
if step.text == "Five" && !strings.Contains(got.DocumentCaretText, "Apples") {
|
|
t.Fatalf("row header missing: %q", got.DocumentCaretText)
|
|
}
|
|
if step.text == "Five" && (got.DocumentTableRowHeader != "Apples" || got.DocumentTableColumnHeader != "Count") {
|
|
t.Fatalf("table header metadata missing for verbosity: %+v", got)
|
|
}
|
|
}
|
|
}
|