Files
cthulhu/internal/app/table_native_test.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)
}
}
}