157 lines
5.4 KiB
Go
157 lines
5.4 KiB
Go
// Copyright (c) 2026 Storm Dragon
|
|
// Authors: Storm Dragon, Angus Kola
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
package i3ipc
|
|
|
|
import (
|
|
"encoding/json"
|
|
"reflect"
|
|
"testing"
|
|
)
|
|
|
|
func ptr[T any](v T) *T { return &v }
|
|
func testWindow() node {
|
|
return node{ID: 10, Window: 100, Name: "Editor", Focused: true, Marks: []string{}, Urgent: ptr(false), Fullscreen: ptr(0), Floating: "auto_off"}
|
|
}
|
|
func testRoot(n node, ws uint64, name string) node {
|
|
return node{ID: 1, Type: "root", Nodes: []node{{ID: ws, Type: "workspace", Name: name, Nodes: []node{n}}}}
|
|
}
|
|
func applyTest(t *testing.T, s *state, kind uint32, change string, n node, root node) []Event {
|
|
t.Helper()
|
|
data := map[string]any{"change": change, "container": n, "current": n}
|
|
raw, _ := json.Marshal(data)
|
|
got, err := s.apply(message{kind, raw}, func() (node, error) { return root, nil })
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return got
|
|
}
|
|
func TestConfirmedStateChanges(t *testing.T) {
|
|
n := testWindow()
|
|
var s state
|
|
root := testRoot(n, 2, "A")
|
|
s.baseline(root, []workspace{{ID: 2, Name: "A", Focused: true}})
|
|
if got := applyTest(t, &s, windowEvent, "focus", n, root); len(got) != 0 {
|
|
t.Fatal(got)
|
|
}
|
|
n.Fullscreen = ptr(1)
|
|
if got := applyTest(t, &s, windowEvent, "fullscreen_mode", n, root); len(got) != 1 || got[0].Kind != Fullscreen || !got[0].Enabled {
|
|
t.Fatal(got)
|
|
}
|
|
n.Fullscreen = ptr(0)
|
|
n.Focused = false
|
|
if got := applyTest(t, &s, windowEvent, "fullscreen_mode", n, root); len(got) != 1 || got[0].Enabled || got[0].Window != "Editor" || got[0].Focused {
|
|
t.Fatal(got)
|
|
}
|
|
n.Floating = "user_on"
|
|
if got := applyTest(t, &s, windowEvent, "floating", n, root); len(got) != 1 || got[0].Kind != Floating || !got[0].Enabled {
|
|
t.Fatal(got)
|
|
}
|
|
n.Floating = "user_off"
|
|
if got := applyTest(t, &s, windowEvent, "floating", n, root); len(got) != 1 || got[0].Enabled {
|
|
t.Fatal(got)
|
|
}
|
|
n.Urgent = ptr(true)
|
|
root = testRoot(n, 2, "A")
|
|
if got := applyTest(t, &s, windowEvent, "urgent", n, root); len(got) != 1 || got[0].Kind != Urgent || got[0].Workspace != "A" {
|
|
t.Fatal(got)
|
|
}
|
|
if got := applyTest(t, &s, windowEvent, "urgent", n, root); len(got) != 0 {
|
|
t.Fatal(got)
|
|
}
|
|
if got := applyTest(t, &s, workspaceEvent, "urgent", root.Nodes[0], root); len(got) != 0 {
|
|
t.Fatal(got)
|
|
}
|
|
n.Urgent = ptr(false)
|
|
if got := applyTest(t, &s, windowEvent, "urgent", n, root); len(got) != 0 {
|
|
t.Fatal(got)
|
|
}
|
|
n.Marks = []string{"z", "a"}
|
|
got := applyTest(t, &s, windowEvent, "mark", n, root)
|
|
if len(got) != 2 || got[0].Name != "a" || got[1].Name != "z" || got[0].Kind != MarkAdded {
|
|
t.Fatal(got)
|
|
}
|
|
n.Marks = []string{"a", "z"}
|
|
if got := applyTest(t, &s, windowEvent, "mark", n, root); len(got) != 0 {
|
|
t.Fatal(got)
|
|
}
|
|
n.Marks = []string{}
|
|
if got := applyTest(t, &s, windowEvent, "mark", n, root); len(got) != 2 || got[0].Kind != MarkRemoved {
|
|
t.Fatal(got)
|
|
}
|
|
if got := applyTest(t, &s, windowEvent, "move", n, root); len(got) != 0 {
|
|
t.Fatal(got)
|
|
}
|
|
root = testRoot(n, 3, "résumé & 99")
|
|
if got := applyTest(t, &s, windowEvent, "move", n, root); len(got) != 1 || got[0].Kind != WorkspaceMove || got[0].Workspace != "résumé & 99" {
|
|
t.Fatal(got)
|
|
}
|
|
root = testRoot(n, 4, "__i3_scratch")
|
|
if got := applyTest(t, &s, windowEvent, "move", n, root); len(got) != 0 {
|
|
t.Fatal(got)
|
|
}
|
|
root = testRoot(n, 3, "B")
|
|
if got := applyTest(t, &s, windowEvent, "move", n, root); len(got) != 0 {
|
|
t.Fatal(got)
|
|
}
|
|
}
|
|
func TestWorkspacesAndModes(t *testing.T) {
|
|
n := testWindow()
|
|
var s state
|
|
s.baseline(testRoot(n, 2, "A"), []workspace{{ID: 2, Focused: true}})
|
|
for _, change := range []string{"focus", "rename"} {
|
|
ws := node{ID: 2, Name: "empty & arbitrary", Type: "workspace"}
|
|
if got := applyTest(t, &s, workspaceEvent, change, ws, node{}); len(got) != 1 || got[0].Kind != Workspace || got[0].Name != ws.Name {
|
|
t.Fatal(got)
|
|
}
|
|
}
|
|
if got := applyTest(t, &s, workspaceEvent, "rename", node{ID: 3, Name: "background"}, node{}); len(got) != 0 {
|
|
t.Fatal(got)
|
|
}
|
|
raw := []byte(`{"change":"<b>personal & more</b>","pango_markup":true}`)
|
|
got, err := s.apply(message{modeEvent, raw}, nil)
|
|
want := []Event{{Kind: Mode, Name: "<b>personal & more</b>", Markup: true}}
|
|
if err != nil || !reflect.DeepEqual(got, want) {
|
|
t.Fatalf("%+v %v", got, err)
|
|
}
|
|
}
|
|
func TestUnknownContextAndMissingEvidence(t *testing.T) {
|
|
n := testWindow()
|
|
var s state
|
|
s.baseline(node{}, nil)
|
|
if got := applyTest(t, &s, windowEvent, "mark", n, node{}); len(got) != 0 {
|
|
t.Fatal(got)
|
|
}
|
|
if got := applyTest(t, &s, windowEvent, "move", n, testRoot(n, 3, "B")); len(got) != 0 {
|
|
t.Fatal(got)
|
|
}
|
|
n.Fullscreen = nil
|
|
n.Floating = "unknown"
|
|
for _, change := range []string{"fullscreen_mode", "floating", "new", "close", "title"} {
|
|
if got := applyTest(t, &s, windowEvent, change, n, node{}); len(got) != 0 {
|
|
t.Fatal(got)
|
|
}
|
|
}
|
|
n = testWindow()
|
|
n.Urgent = ptr(true)
|
|
if got := applyTest(t, &s, windowEvent, "urgent", n, node{Type: "root"}); len(got) != 1 || got[0].Workspace != "" {
|
|
t.Fatal(got)
|
|
}
|
|
}
|
|
|
|
func TestScratchpadImplicitFloatingSuppressed(t *testing.T) {
|
|
n := testWindow()
|
|
var s state
|
|
s.baseline(testRoot(n, 2, "A"), nil)
|
|
n.Floating = "user_on" // i3 reports this before assigning the scratchpad.
|
|
if got := applyTest(t, &s, windowEvent, "floating", n, testRoot(n, 4, "__i3_scratch")); len(got) != 0 {
|
|
t.Fatal(got)
|
|
}
|
|
if got := applyTest(t, &s, windowEvent, "move", n, testRoot(n, 4, "__i3_scratch")); len(got) != 0 {
|
|
t.Fatal(got)
|
|
}
|
|
if got := applyTest(t, &s, windowEvent, "move", n, testRoot(n, 2, "A")); len(got) != 0 {
|
|
t.Fatal(got)
|
|
}
|
|
}
|