Files
cthulhu/internal/app/panel_applet_native_test.go

116 lines
3.3 KiB
Go

// Copyright (c) 2026 Storm Dragon
// Authors: Storm Dragon, Angus Kola
// SPDX-License-Identifier: GPL-3.0-or-later
package app
import (
"bufio"
"context"
"fmt"
"io"
"log/slog"
"os"
"os/exec"
"strings"
"testing"
"time"
"git.stormux.org/storm/cthulhu/internal/atspi"
"git.stormux.org/storm/cthulhu/internal/braille"
)
func TestNativeEmbeddedPanelAppletPresentation(t *testing.T) {
if os.Getenv("CTHULHU_NATIVE_PANEL_APPLET") != "1" {
t.Skip("requires private Xorg/AT-SPI session")
}
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
defer cancel()
adapter := atspi.New()
if err := adapter.Start(ctx); err != nil {
t.Fatal(err)
}
defer adapter.Stop(context.Background())
start := func(args ...string) (io.WriteCloser, string, string) {
t.Helper()
command := exec.CommandContext(ctx, "/usr/bin/python3", append([]string{"../../tests/gtk_panel_applet.py"}, args...)...)
input, err := command.StdinPipe()
if err != nil {
t.Fatal(err)
}
output, err := command.StdoutPipe()
if err != nil {
t.Fatal(err)
}
command.Stderr = os.Stderr
if err := command.Start(); err != nil {
t.Fatal(err)
}
t.Cleanup(func() { input.Close(); command.Process.Kill(); command.Wait() })
line, err := bufio.NewReader(output).ReadString('\n')
fields := strings.Fields(line)
if err != nil || len(fields) != 3 || fields[0] != "ready" {
t.Fatalf("fixture readiness: %q, %v", line, err)
}
return input, fields[1], fields[2]
}
host, xid, socketID := start("panel")
start("applet", socketID)
editor, editorXID, _ := start("editor")
key := func(args ...string) {
t.Helper()
if output, err := exec.CommandContext(ctx, "xdotool", args...).CombinedOutput(); err != nil {
t.Fatalf("xdotool: %s, %v", output, err)
}
}
speech := &fakeSpeechQueue{spoken: make(chan string, 128)}
frames := make(chan braille.Frame, 128)
done := make(chan struct{})
go func() {
defer close(done)
processEventsWithInput(ctx, slog.New(slog.DiscardHandler),
outputQueue{speech: speech, braille: browserBrailleProbe{frames: frames}},
adapter, nil, nil, 0, nil, nil, nil, nil)
}()
defer func() { cancel(); <-done }()
wait := func(name string) {
t.Helper()
spoken, displayed := false, false
var messages []string
for !spoken || !displayed {
select {
case text := <-speech.spoken:
messages = append(messages, text)
spoken = spoken || strings.Contains(text, name)
case frame := <-frames:
displayed = displayed || strings.Contains(frame.Text, name)
case <-ctx.Done():
t.Fatalf("missing presentation %q: speech=%v braille=%v messages=%q", name, spoken, displayed, messages)
}
}
}
for iteration := 0; iteration < 2; iteration++ {
key("windowfocus", "--sync", xid)
fmt.Fprintln(host, "first")
wait("Before applet")
key("key", "Tab")
wait("Network applet")
key("key", "Tab")
wait("Applet settings")
key("key", "shift+Tab")
wait("Network applet")
key("key", "Tab")
wait("Applet settings")
key("key", "Tab")
wait("After applet")
for _, name := range []string{"Applet settings", "Network applet", "Before applet"} {
key("key", "shift+Tab")
wait(name)
}
key("windowfocus", "--sync", editorXID)
text := fmt.Sprintf("Returned to editor %d", iteration)
fmt.Fprintln(editor, "edit "+text)
wait(text)
}
}