- Web-based key editor (Alpine.js SPA) - Actions: shell commands, scripts, built-in media/volume/brightness, page switching - Keyboard shortcut action (wtype/xdotool) - On-device display with periodic command output - Auto page switching (Hyprland, Sway, Niri, GNOME, KDE, X11) - Screensaver with idle detection - Config hot-reload - Multi-device support
224 lines
5.0 KiB
Go
224 lines
5.0 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"testing"
|
|
|
|
"streamdeck-lets-go/internal/config"
|
|
)
|
|
|
|
func TestFindFocusedSwayNode(t *testing.T) {
|
|
input := `{
|
|
"id": 0,
|
|
"type": "root",
|
|
"nodes": [
|
|
{
|
|
"type": "output",
|
|
"name": "eDP-1",
|
|
"nodes": [
|
|
{
|
|
"type": "workspace",
|
|
"name": "1",
|
|
"nodes": [
|
|
{
|
|
"type": "con",
|
|
"focused": false,
|
|
"app_id": "firefox",
|
|
"name": "Firefox",
|
|
"nodes": []
|
|
}
|
|
]
|
|
},
|
|
{
|
|
"type": "workspace",
|
|
"name": "2",
|
|
"nodes": [
|
|
{
|
|
"type": "con",
|
|
"focused": true,
|
|
"app_id": "Alacritty",
|
|
"name": "Alacritty",
|
|
"nodes": []
|
|
}
|
|
]
|
|
}
|
|
],
|
|
"floating_nodes": []
|
|
}
|
|
]
|
|
}`
|
|
|
|
var root swayNode
|
|
if err := json.Unmarshal([]byte(input), &root); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
node := findFocusedSwayNode(&root)
|
|
if node == nil {
|
|
t.Fatal("expected focused node, got nil")
|
|
}
|
|
if node.AppID == nil || *node.AppID != "Alacritty" {
|
|
t.Fatalf("expected Alacritty, got %v", node.AppID)
|
|
}
|
|
if node.Name != "Alacritty" {
|
|
t.Fatalf("expected name Alacritty, got %s", node.Name)
|
|
}
|
|
}
|
|
|
|
func TestFindFocusedSwayNode_XWayland(t *testing.T) {
|
|
input := `{
|
|
"id": 0,
|
|
"type": "root",
|
|
"nodes": [
|
|
{
|
|
"type": "output",
|
|
"nodes": [
|
|
{
|
|
"type": "workspace",
|
|
"nodes": [
|
|
{
|
|
"type": "con",
|
|
"focused": true,
|
|
"app_id": null,
|
|
"name": "Steam",
|
|
"window_properties": {"class": "steam"},
|
|
"nodes": []
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}`
|
|
|
|
var root swayNode
|
|
json.Unmarshal([]byte(input), &root)
|
|
|
|
node := findFocusedSwayNode(&root)
|
|
if node == nil {
|
|
t.Fatal("expected focused node, got nil")
|
|
}
|
|
if node.Props == nil || node.Props.Class != "steam" {
|
|
t.Fatal("expected window_properties.class = steam")
|
|
}
|
|
}
|
|
|
|
func TestFindFocusedSwayNode_NoFocus(t *testing.T) {
|
|
input := `{
|
|
"id": 0,
|
|
"type": "root",
|
|
"nodes": [
|
|
{
|
|
"type": "output",
|
|
"nodes": [
|
|
{
|
|
"type": "workspace",
|
|
"nodes": [
|
|
{
|
|
"type": "con",
|
|
"focused": false,
|
|
"app_id": "kitty",
|
|
"name": "kitty",
|
|
"nodes": []
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}
|
|
]
|
|
}`
|
|
|
|
var root swayNode
|
|
json.Unmarshal([]byte(input), &root)
|
|
node := findFocusedSwayNode(&root)
|
|
if node != nil {
|
|
t.Fatal("expected nil when no focused node")
|
|
}
|
|
}
|
|
|
|
func TestAutoSwitchManager_Evaluate(t *testing.T) {
|
|
rules := []config.SwitchRule{
|
|
{WMClass: "firefox", Page: "browser"},
|
|
{WMClass: "Alacritty|kitty", Title: ".*vim.*", Page: "coding", Stay: true},
|
|
}
|
|
|
|
m := NewAutoSwitchManager(rules, "default")
|
|
|
|
page, ok := m.Evaluate(Window{WMClass: "firefox", Title: "Mozilla Firefox"}, "default")
|
|
if !ok || page != "browser" {
|
|
t.Fatalf("expected browser, got %s/%v", page, ok)
|
|
}
|
|
|
|
page, ok = m.Evaluate(Window{WMClass: "Alacritty", Title: "nvim main.go"}, "default")
|
|
if !ok || page != "coding" {
|
|
t.Fatalf("expected coding, got %s/%v", page, ok)
|
|
}
|
|
|
|
page, ok = m.Evaluate(Window{WMClass: "firefox", Title: "YouTube"}, "browser")
|
|
if !ok || page != "browser" {
|
|
t.Fatalf("expected browser again, got %s/%v", page, ok)
|
|
}
|
|
}
|
|
|
|
func TestAutoSwitchManager_Stay(t *testing.T) {
|
|
rules := []config.SwitchRule{
|
|
{WMClass: "firefox", Page: "browser", Stay: false},
|
|
}
|
|
|
|
m := NewAutoSwitchManager(rules, "home")
|
|
|
|
m.Evaluate(Window{WMClass: "firefox", Title: "Mozilla Firefox"}, "home")
|
|
|
|
page, ok := m.Evaluate(Window{WMClass: "thunar", Title: "Files"}, "browser")
|
|
if !ok || page != "home" {
|
|
t.Fatalf("expected revert to home, got %s/%v", page, ok)
|
|
}
|
|
}
|
|
|
|
func TestAutoSwitchManager_StayTrue(t *testing.T) {
|
|
rules := []config.SwitchRule{
|
|
{WMClass: "firefox", Page: "browser", Stay: true},
|
|
}
|
|
|
|
m := NewAutoSwitchManager(rules, "home")
|
|
|
|
m.Evaluate(Window{WMClass: "firefox", Title: "Mozilla Firefox"}, "home")
|
|
|
|
page, ok := m.Evaluate(Window{WMClass: "thunar", Title: "Files"}, "browser")
|
|
if ok {
|
|
t.Fatalf("expected no switch (stay=true), got %s", page)
|
|
}
|
|
}
|
|
|
|
func TestAutoSwitchManager_ManualOverride(t *testing.T) {
|
|
rules := []config.SwitchRule{
|
|
{WMClass: "firefox", Page: "browser", Stay: false},
|
|
}
|
|
|
|
m := NewAutoSwitchManager(rules, "home")
|
|
|
|
// 1. Firefox opens → auto-switch to "browser"
|
|
m.Evaluate(Window{WMClass: "firefox", Title: "Mozilla Firefox"}, "home")
|
|
|
|
// 2. User manually switches to "settings"
|
|
m.NotifyManualPage("settings")
|
|
|
|
// 3. Window changes to thunar on "settings" → no match → stay on settings
|
|
page, ok := m.Evaluate(Window{WMClass: "thunar", Title: "Files"}, "settings")
|
|
if ok {
|
|
t.Fatalf("expected no switch (already on manual page), got %s", page)
|
|
}
|
|
|
|
// 4. Firefox comes back → auto-switch to "browser" again
|
|
page, ok = m.Evaluate(Window{WMClass: "firefox", Title: "Mozilla Firefox"}, "settings")
|
|
if !ok || page != "browser" {
|
|
t.Fatalf("expected browser, got %s/%v", page, ok)
|
|
}
|
|
|
|
// 5. Thunar again, current page is "browser" (auto) → no match → revert to "settings"
|
|
page, ok = m.Evaluate(Window{WMClass: "thunar", Title: "Files"}, "browser")
|
|
if !ok || page != "settings" {
|
|
t.Fatalf("expected revert to settings, got %s/%v", page, ok)
|
|
}
|
|
}
|