Files
groundstation/internal/display/x11_test.go
T
piotr b169a64ad5
Build Ground Station / build (push) Successful in 44s
feat: show no-WFB kiosk fallback only while video absent (#2)
2026-10-01 14:40:24 +02:00

192 lines
6.5 KiB
Go

package display
import (
"errors"
"strings"
"testing"
"github.com/BurntSushi/xgb/xproto"
)
func TestX11FullscreenArgsKeepCanvasBelowVideo(t *testing.T) {
args := x11FullscreenArgs(0x1a2b)
joined := strings.Join(args, " ")
if !strings.Contains(joined, "add,fullscreen,below") {
t.Fatalf("args %v must request fullscreen with below stacking", args)
}
if !strings.Contains(joined, "0x00001a2b") {
t.Fatalf("args %v must reference the kiosk window id", args)
}
}
func TestX11SessionRedrawsOnlyOnExpose(t *testing.T) {
if !x11WantsRedraw(xproto.ExposeEvent{}) {
t.Fatal("expose events must trigger a redraw")
}
if x11WantsRedraw(xproto.MapNotifyEvent{}) {
t.Fatal("map notify events must not trigger a redraw")
}
}
func TestX11OpenerAppliesFullscreenArgsThroughHook(t *testing.T) {
var got [][]string
opener := X11Opener{Fullscreen: func(args []string) error {
got = append(got, args)
return errors.New("wmctrl missing")
}}
if err := opener.applyFullscreen(x11FullscreenArgs(0x42)); err == nil {
t.Fatal("hook error must propagate for logging")
}
if len(got) != 1 || !strings.Contains(strings.Join(got[0], " "), "add,fullscreen,below") {
t.Fatalf("hook args %v must request fullscreen with below stacking", got)
}
}
func TestClassifyVideoChangeTracksVideoWindowLifecycle(t *testing.T) {
const own = xproto.Window(0x10)
const video = xproto.Window(0x20)
const other = xproto.Window(0x30)
change, w := classifyVideoChange(xproto.MapNotifyEvent{Window: video}, own)
if change != videoAppeared || w != video {
t.Fatalf("map of a foreign window must announce video, got %v %v", change, w)
}
change, _ = classifyVideoChange(xproto.MapNotifyEvent{Window: own}, own)
if change != videoUnrelated {
t.Fatalf("map of the kiosk window must be unrelated, got %v", change)
}
change, w = classifyVideoChange(xproto.UnmapNotifyEvent{Window: video}, own)
if change != videoDisappeared || w != video {
t.Fatalf("unmap must announce a video end candidate, got %v %v", change, w)
}
change, _ = classifyVideoChange(xproto.UnmapNotifyEvent{Window: own}, own)
if change != videoUnrelated {
t.Fatalf("unmap of the kiosk window must be unrelated, got %v", change)
}
change, w = classifyVideoChange(xproto.UnmapNotifyEvent{Window: other}, own)
if change != videoDisappeared || w != other {
t.Fatalf("unmap of a foreign window must be an end candidate, got %v %v", change, w)
}
change, w = classifyVideoChange(xproto.DestroyNotifyEvent{Window: video}, own)
if change != videoDisappeared || w != video {
t.Fatalf("destroy must announce a video end candidate, got %v %v", change, w)
}
change, _ = classifyVideoChange(xproto.ExposeEvent{}, own)
if change != videoUnrelated {
t.Fatalf("expose must not change video presence, got %v", change)
}
}
func TestAddAndRemoveVideoWindowTrackPresenceTransitions(t *testing.T) {
const video = xproto.Window(0x20)
const second = xproto.Window(0x30)
windows := make(map[xproto.Window]struct{})
if !addVideoWindow(windows, video) {
t.Fatal("first video window must change presence")
}
if addVideoWindow(windows, video) {
t.Fatal("re-adding the tracked window must not change presence")
}
if addVideoWindow(windows, second) {
t.Fatal("a second video window must not change presence")
}
if removeVideoWindow(windows, second) {
t.Fatal("removing one of two video windows must not change presence")
}
if !removeVideoWindow(windows, video) {
t.Fatal("removing the last video window must change presence")
}
if removeVideoWindow(windows, video) {
t.Fatal("removing an untracked window must not change presence")
}
if len(windows) != 0 {
t.Fatalf("set %v must be empty after all video ended", windows)
}
}
func TestEWMHNonVideoWindowTypesExcludeDesktopHelpers(t *testing.T) {
for _, name := range []string{"DOCK", "DESKTOP", "TOOLBAR", "MENU", "UTILITY", "SPLASH", "DIALOG", "NOTIFICATION", "TOOLTIP"} {
if !ewmhNonVideoTypes[name] {
t.Fatalf("window type %q must not count as video", name)
}
}
for _, name := range []string{"NORMAL", "", "VIDEO_PLAY"} {
if ewmhNonVideoTypes[name] {
t.Fatalf("window type %q must count as video", name)
}
}
}
func TestUTF16Char2bEncodesPolishFallbackText(t *testing.T) {
chars := utf16Char2b(wantFallbackText, maxImageText16Units)
want := []xproto.Char2b{
{Byte1: 0, Byte2: 'B'}, {Byte1: 0, Byte2: 'r'}, {Byte1: 0, Byte2: 'a'}, {Byte1: 0, Byte2: 'k'}, {Byte1: 0, Byte2: ' '},
{Byte1: 0, Byte2: 'p'}, {Byte1: 0, Byte2: 'o'},
{Byte1: 0x01, Byte2: 0x42}, // ł
{Byte1: 0x01, Byte2: 0x05}, // ą
{Byte1: 0, Byte2: 'c'}, {Byte1: 0, Byte2: 'z'}, {Byte1: 0, Byte2: 'e'}, {Byte1: 0, Byte2: 'n'}, {Byte1: 0, Byte2: 'i'}, {Byte1: 0, Byte2: 'a'}, {Byte1: 0, Byte2: ' '},
{Byte1: 0, Byte2: 'W'}, {Byte1: 0, Byte2: 'F'}, {Byte1: 0, Byte2: 'B'},
}
if len(chars) != len(want) {
t.Fatalf("units=%v want %v", chars, want)
}
for i := range want {
if chars[i] != want[i] {
t.Fatalf("unit %d=%v want %v", i, chars[i], want[i])
}
}
}
func TestUTF16Char2bTruncatesToOneRequestLength(t *testing.T) {
if chars := utf16Char2b(wantFallbackText, 10); len(chars) != 10 {
t.Fatalf("units=%d want 10", len(chars))
}
}
func TestUTF16Char2bNeverSplitsSurrogatePairs(t *testing.T) {
// U+1F3A5 encodes as a surrogate pair (0xD83C, 0xDFA5): a cut that would
// land between the two units must back off instead of splitting it.
if chars := utf16Char2b("a🎥", 2); len(chars) != 1 || chars[0].Byte2 != 'a' {
t.Fatalf("units=%v want only the leading ASCII unit", chars)
}
if chars := utf16Char2b("🎥", 1); len(chars) != 0 {
t.Fatalf("units=%v want none, the pair does not fit", chars)
}
if chars := utf16Char2b("🎥", 2); len(chars) != 2 {
t.Fatalf("units=%v want the intact pair", chars)
}
if chars := utf16Char2b(wantFallbackText, 0); len(chars) != 0 {
t.Fatalf("units=%v want none for a zero limit", chars)
}
}
func TestFoldToASCIIStripsPolishDiacritics(t *testing.T) {
if got := foldToASCII(wantFallbackText); got != "Brak polaczenia WFB" {
t.Fatalf("foldToASCII=%q want %q", got, "Brak polaczenia WFB")
}
}
func TestFoldToASCIIKeepsPlainASCIIUntouched(t *testing.T) {
if got := foldToASCII("plain text"); got != "plain text" {
t.Fatalf("foldToASCII=%q want %q", got, "plain text")
}
}
func TestX11FontPatternRequestsUnicodeFixedFont(t *testing.T) {
if !strings.Contains(iso10646FontPattern, "fixed") {
t.Fatalf("font pattern %q must select a fixed font", iso10646FontPattern)
}
if !strings.Contains(iso10646FontPattern, "iso10646") {
t.Fatalf("font pattern %q must select a Unicode encoding for the exact fallback text", iso10646FontPattern)
}
}