gui/pkg/window/service_test.go
Virgil 5ce1d67f70
Some checks failed
Security Scan / security (push) Failing after 28s
Test / test (push) Successful in 1m21s
refactor(ax): add atomic window bounds task
2026-03-31 08:23:59 +00:00

638 lines
18 KiB
Go

package window
import (
"context"
"sync"
"testing"
"forge.lthn.ai/core/go/pkg/core"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func newTestWindowService(t *testing.T) (*Service, *core.Core) {
t.Helper()
t.Setenv("XDG_CONFIG_HOME", t.TempDir())
c, err := core.New(
core.WithService(Register(newMockPlatform())),
core.WithServiceLock(),
)
require.NoError(t, err)
require.NoError(t, c.ServiceStartup(context.Background(), nil))
svc := core.MustServiceFor[*Service](c, "window")
return svc, c
}
func TestRegister_Good(t *testing.T) {
svc, _ := newTestWindowService(t)
assert.NotNil(t, svc)
assert.NotNil(t, svc.manager)
}
func TestTaskOpenWindow_Good(t *testing.T) {
_, c := newTestWindowService(t)
result, handled, err := c.PERFORM(TaskOpenWindow{
Window: Window{Name: "test", URL: "/"},
})
require.NoError(t, err)
assert.True(t, handled)
info := result.(WindowInfo)
assert.Equal(t, "test", info.Name)
}
func TestTaskOpenWindow_Declarative_Good(t *testing.T) {
_, c := newTestWindowService(t)
result, handled, err := c.PERFORM(TaskOpenWindow{
Window: Window{Name: "test-fallback", URL: "/"},
})
require.NoError(t, err)
assert.True(t, handled)
info := result.(WindowInfo)
assert.Equal(t, "test-fallback", info.Name)
}
func TestTaskOpenWindow_Bad(t *testing.T) {
// No window service registered — PERFORM returns handled=false
c, err := core.New(core.WithServiceLock())
require.NoError(t, err)
_, handled, _ := c.PERFORM(TaskOpenWindow{})
assert.False(t, handled)
}
func TestQueryWindowList_Good(t *testing.T) {
_, c := newTestWindowService(t)
_, _, _ = c.PERFORM(TaskOpenWindow{Window: Window{Name: "a"}})
_, _, _ = c.PERFORM(TaskOpenWindow{Window: Window{Name: "b"}})
result, handled, err := c.QUERY(QueryWindowList{})
require.NoError(t, err)
assert.True(t, handled)
list := result.([]WindowInfo)
assert.Len(t, list, 2)
}
func TestQueryWindowByName_Good(t *testing.T) {
_, c := newTestWindowService(t)
_, _, _ = c.PERFORM(TaskOpenWindow{Window: Window{Name: "test"}})
result, handled, err := c.QUERY(QueryWindowByName{Name: "test"})
require.NoError(t, err)
assert.True(t, handled)
info := result.(*WindowInfo)
assert.Equal(t, "test", info.Name)
}
func TestQueryWindowByName_Bad(t *testing.T) {
_, c := newTestWindowService(t)
result, handled, err := c.QUERY(QueryWindowByName{Name: "nonexistent"})
require.NoError(t, err)
assert.True(t, handled) // handled=true, result is nil (not found)
assert.Nil(t, result)
}
func TestQuerySavedWindowStates_Good(t *testing.T) {
_, c := newTestWindowService(t)
_, _, _ = c.PERFORM(TaskOpenWindow{Window: Window{Name: "test"}})
_, _, _ = c.PERFORM(TaskSetPosition{Name: "test", X: 120, Y: 240})
result, handled, err := c.QUERY(QuerySavedWindowStates{})
require.NoError(t, err)
assert.True(t, handled)
states := result.(map[string]WindowState)
require.Contains(t, states, "test")
assert.Equal(t, 120, states["test"].X)
assert.Equal(t, 240, states["test"].Y)
}
func TestTaskCloseWindow_Good(t *testing.T) {
_, c := newTestWindowService(t)
_, _, _ = c.PERFORM(TaskOpenWindow{Window: Window{Name: "test"}})
var closedCount int
c.RegisterAction(func(_ *core.Core, msg core.Message) error {
if _, ok := msg.(ActionWindowClosed); ok {
closedCount++
}
return nil
})
_, handled, err := c.PERFORM(TaskCloseWindow{Name: "test"})
require.NoError(t, err)
assert.True(t, handled)
assert.Equal(t, 1, closedCount)
// Verify window is removed
result, _, _ := c.QUERY(QueryWindowByName{Name: "test"})
assert.Nil(t, result)
}
func TestTaskCloseWindow_Bad(t *testing.T) {
_, c := newTestWindowService(t)
_, handled, err := c.PERFORM(TaskCloseWindow{Name: "nonexistent"})
assert.True(t, handled)
assert.Error(t, err)
}
func TestTaskResetWindowState_Good(t *testing.T) {
_, c := newTestWindowService(t)
_, _, _ = c.PERFORM(TaskOpenWindow{Window: Window{Name: "test"}})
_, _, _ = c.PERFORM(TaskSetPosition{Name: "test", X: 80, Y: 90})
_, handled, err := c.PERFORM(TaskResetWindowState{})
require.NoError(t, err)
assert.True(t, handled)
result, _, err := c.QUERY(QuerySavedWindowStates{})
require.NoError(t, err)
assert.Empty(t, result.(map[string]WindowState))
}
func TestTaskSetPosition_Good(t *testing.T) {
_, c := newTestWindowService(t)
_, _, _ = c.PERFORM(TaskOpenWindow{Window: Window{Name: "test"}})
_, handled, err := c.PERFORM(TaskSetPosition{Name: "test", X: 100, Y: 200})
require.NoError(t, err)
assert.True(t, handled)
result, _, _ := c.QUERY(QueryWindowByName{Name: "test"})
info := result.(*WindowInfo)
assert.Equal(t, 100, info.X)
assert.Equal(t, 200, info.Y)
}
func TestTaskSetSize_Good(t *testing.T) {
_, c := newTestWindowService(t)
_, _, _ = c.PERFORM(TaskOpenWindow{Window: Window{Name: "test"}})
_, handled, err := c.PERFORM(TaskSetSize{Name: "test", Width: 800, Height: 600})
require.NoError(t, err)
assert.True(t, handled)
result, _, _ := c.QUERY(QueryWindowByName{Name: "test"})
info := result.(*WindowInfo)
assert.Equal(t, 800, info.Width)
assert.Equal(t, 600, info.Height)
}
func TestTaskSetBounds_Good(t *testing.T) {
_, c := newTestWindowService(t)
_, _, _ = c.PERFORM(TaskOpenWindow{Window: Window{Name: "test"}})
_, handled, err := c.PERFORM(TaskSetBounds{
Name: "test",
X: 100,
Y: 200,
Width: 800,
Height: 600,
})
require.NoError(t, err)
assert.True(t, handled)
result, _, _ := c.QUERY(QueryWindowByName{Name: "test"})
info := result.(*WindowInfo)
assert.Equal(t, 100, info.X)
assert.Equal(t, 200, info.Y)
assert.Equal(t, 800, info.Width)
assert.Equal(t, 600, info.Height)
states, _, _ := c.QUERY(QuerySavedWindowStates{})
saved := states.(map[string]WindowState)
require.Contains(t, saved, "test")
assert.Equal(t, 100, saved["test"].X)
assert.Equal(t, 200, saved["test"].Y)
assert.Equal(t, 800, saved["test"].Width)
assert.Equal(t, 600, saved["test"].Height)
}
func TestTaskMaximize_Good(t *testing.T) {
_, c := newTestWindowService(t)
_, _, _ = c.PERFORM(TaskOpenWindow{Window: Window{Name: "test"}})
_, handled, err := c.PERFORM(TaskMaximize{Name: "test"})
require.NoError(t, err)
assert.True(t, handled)
result, _, _ := c.QUERY(QueryWindowByName{Name: "test"})
info := result.(*WindowInfo)
assert.True(t, info.Maximized)
}
func TestFileDrop_Good(t *testing.T) {
_, c := newTestWindowService(t)
// Open a window
result, _, _ := c.PERFORM(TaskOpenWindow{
Window: Window{Name: "drop-test"},
})
info := result.(WindowInfo)
assert.Equal(t, "drop-test", info.Name)
// Capture broadcast actions
var dropped ActionFilesDropped
var mu sync.Mutex
c.RegisterAction(func(_ *core.Core, msg core.Message) error {
if a, ok := msg.(ActionFilesDropped); ok {
mu.Lock()
dropped = a
mu.Unlock()
}
return nil
})
// Get the mock window and simulate file drop
svc := core.MustServiceFor[*Service](c, "window")
pw, ok := svc.Manager().Get("drop-test")
require.True(t, ok)
mw := pw.(*mockWindow)
mw.emitFileDrop([]string{"/tmp/file1.txt", "/tmp/file2.txt"}, "upload-zone")
mu.Lock()
assert.Equal(t, "drop-test", dropped.Name)
assert.Equal(t, []string{"/tmp/file1.txt", "/tmp/file2.txt"}, dropped.Paths)
assert.Equal(t, "upload-zone", dropped.TargetID)
mu.Unlock()
}
func TestWindowResizeEvent_UsesCanonicalPayload_Good(t *testing.T) {
svc, c := newTestWindowService(t)
_, _, _ = c.PERFORM(TaskOpenWindow{Window: Window{Name: "resize-test"}})
var (
resized ActionWindowResized
seen bool
)
c.RegisterAction(func(_ *core.Core, msg core.Message) error {
if a, ok := msg.(ActionWindowResized); ok {
resized = a
seen = true
}
return nil
})
pw, ok := svc.Manager().Get("resize-test")
require.True(t, ok)
mw := pw.(*mockWindow)
mw.emit(WindowEvent{
Type: "resize",
Name: "resize-test",
Data: map[string]any{"width": 111, "height": 222},
})
assert.True(t, seen)
assert.Equal(t, "resize-test", resized.Name)
assert.Equal(t, 111, resized.Width)
assert.Equal(t, 222, resized.Height)
resized = ActionWindowResized{}
seen = false
mw.emit(WindowEvent{
Type: "resize",
Name: "resize-test",
Data: map[string]any{"w": 333, "h": 444},
})
assert.True(t, seen)
assert.Equal(t, "resize-test", resized.Name)
assert.Equal(t, 0, resized.Width)
assert.Equal(t, 0, resized.Height)
}
// --- TaskMinimize ---
func TestTaskMinimize_Good(t *testing.T) {
svc, c := newTestWindowService(t)
_, _, _ = c.PERFORM(TaskOpenWindow{Window: Window{Name: "test"}})
_, handled, err := c.PERFORM(TaskMinimize{Name: "test"})
require.NoError(t, err)
assert.True(t, handled)
pw, ok := svc.Manager().Get("test")
require.True(t, ok)
mw := pw.(*mockWindow)
assert.True(t, mw.minimised)
}
func TestTaskMinimize_Bad(t *testing.T) {
_, c := newTestWindowService(t)
_, handled, err := c.PERFORM(TaskMinimize{Name: "nonexistent"})
assert.True(t, handled)
assert.Error(t, err)
}
// --- TaskFocus ---
func TestTaskFocus_Good(t *testing.T) {
svc, c := newTestWindowService(t)
_, _, _ = c.PERFORM(TaskOpenWindow{Window: Window{Name: "test"}})
_, handled, err := c.PERFORM(TaskFocus{Name: "test"})
require.NoError(t, err)
assert.True(t, handled)
pw, ok := svc.Manager().Get("test")
require.True(t, ok)
mw := pw.(*mockWindow)
assert.True(t, mw.focused)
}
func TestTaskFocus_Bad(t *testing.T) {
_, c := newTestWindowService(t)
_, handled, err := c.PERFORM(TaskFocus{Name: "nonexistent"})
assert.True(t, handled)
assert.Error(t, err)
}
// --- TaskRestore ---
func TestTaskRestore_Good(t *testing.T) {
svc, c := newTestWindowService(t)
_, _, _ = c.PERFORM(TaskOpenWindow{Window: Window{Name: "test"}})
// First maximize, then restore
_, _, _ = c.PERFORM(TaskMaximize{Name: "test"})
_, handled, err := c.PERFORM(TaskRestore{Name: "test"})
require.NoError(t, err)
assert.True(t, handled)
pw, ok := svc.Manager().Get("test")
require.True(t, ok)
mw := pw.(*mockWindow)
assert.False(t, mw.maximised)
// Verify state was updated
state, ok := svc.Manager().State().GetState("test")
assert.True(t, ok)
assert.False(t, state.Maximized)
}
func TestTaskRestore_Bad(t *testing.T) {
_, c := newTestWindowService(t)
_, handled, err := c.PERFORM(TaskRestore{Name: "nonexistent"})
assert.True(t, handled)
assert.Error(t, err)
}
// --- TaskSetTitle ---
func TestTaskSetTitle_Good(t *testing.T) {
svc, c := newTestWindowService(t)
_, _, _ = c.PERFORM(TaskOpenWindow{Window: Window{Name: "test"}})
_, handled, err := c.PERFORM(TaskSetTitle{Name: "test", Title: "New Title"})
require.NoError(t, err)
assert.True(t, handled)
pw, ok := svc.Manager().Get("test")
require.True(t, ok)
assert.Equal(t, "New Title", pw.Title())
}
func TestTaskSetTitle_Bad(t *testing.T) {
_, c := newTestWindowService(t)
_, handled, err := c.PERFORM(TaskSetTitle{Name: "nonexistent", Title: "Nope"})
assert.True(t, handled)
assert.Error(t, err)
}
// --- TaskSetAlwaysOnTop ---
func TestTaskSetAlwaysOnTop_Good(t *testing.T) {
svc, c := newTestWindowService(t)
_, _, _ = c.PERFORM(TaskOpenWindow{Window: Window{Name: "test"}})
_, handled, err := c.PERFORM(TaskSetAlwaysOnTop{Name: "test", AlwaysOnTop: true})
require.NoError(t, err)
assert.True(t, handled)
pw, ok := svc.Manager().Get("test")
require.True(t, ok)
mw := pw.(*mockWindow)
assert.True(t, mw.alwaysOnTop)
}
func TestTaskSetAlwaysOnTop_Bad(t *testing.T) {
_, c := newTestWindowService(t)
_, handled, err := c.PERFORM(TaskSetAlwaysOnTop{Name: "nonexistent", AlwaysOnTop: true})
assert.True(t, handled)
assert.Error(t, err)
}
// --- TaskSetBackgroundColour ---
func TestTaskSetBackgroundColour_Good(t *testing.T) {
svc, c := newTestWindowService(t)
_, _, _ = c.PERFORM(TaskOpenWindow{Window: Window{Name: "test"}})
_, handled, err := c.PERFORM(TaskSetBackgroundColour{
Name: "test", Red: 10, Green: 20, Blue: 30, Alpha: 40,
})
require.NoError(t, err)
assert.True(t, handled)
pw, ok := svc.Manager().Get("test")
require.True(t, ok)
mw := pw.(*mockWindow)
assert.Equal(t, [4]uint8{10, 20, 30, 40}, mw.backgroundColour)
}
func TestTaskSetBackgroundColour_Bad(t *testing.T) {
_, c := newTestWindowService(t)
_, handled, err := c.PERFORM(TaskSetBackgroundColour{Name: "nonexistent", Red: 1, Green: 2, Blue: 3, Alpha: 4})
assert.True(t, handled)
assert.Error(t, err)
}
// --- TaskSetVisibility ---
func TestTaskSetVisibility_Good(t *testing.T) {
svc, c := newTestWindowService(t)
_, _, _ = c.PERFORM(TaskOpenWindow{Window: Window{Name: "test"}})
_, handled, err := c.PERFORM(TaskSetVisibility{Name: "test", Visible: true})
require.NoError(t, err)
assert.True(t, handled)
pw, ok := svc.Manager().Get("test")
require.True(t, ok)
mw := pw.(*mockWindow)
assert.True(t, mw.visible)
// Now hide it
_, handled, err = c.PERFORM(TaskSetVisibility{Name: "test", Visible: false})
require.NoError(t, err)
assert.True(t, handled)
assert.False(t, mw.visible)
}
func TestTaskSetVisibility_Bad(t *testing.T) {
_, c := newTestWindowService(t)
_, handled, err := c.PERFORM(TaskSetVisibility{Name: "nonexistent", Visible: true})
assert.True(t, handled)
assert.Error(t, err)
}
// --- TaskFullscreen ---
func TestTaskFullscreen_Good(t *testing.T) {
svc, c := newTestWindowService(t)
_, _, _ = c.PERFORM(TaskOpenWindow{Window: Window{Name: "test"}})
// Enter fullscreen
_, handled, err := c.PERFORM(TaskFullscreen{Name: "test", Fullscreen: true})
require.NoError(t, err)
assert.True(t, handled)
pw, ok := svc.Manager().Get("test")
require.True(t, ok)
mw := pw.(*mockWindow)
assert.True(t, mw.fullscreened)
// Exit fullscreen
_, handled, err = c.PERFORM(TaskFullscreen{Name: "test", Fullscreen: false})
require.NoError(t, err)
assert.True(t, handled)
assert.False(t, mw.fullscreened)
}
func TestTaskFullscreen_Bad(t *testing.T) {
_, c := newTestWindowService(t)
_, handled, err := c.PERFORM(TaskFullscreen{Name: "nonexistent", Fullscreen: true})
assert.True(t, handled)
assert.Error(t, err)
}
// --- TaskSaveLayout ---
func TestTaskSaveLayout_Good(t *testing.T) {
svc, c := newTestWindowService(t)
_, _, _ = c.PERFORM(TaskOpenWindow{Window: Window{Name: "editor", Width: 960, Height: 1080, X: 0, Y: 0}})
_, _, _ = c.PERFORM(TaskOpenWindow{Window: Window{Name: "terminal", Width: 960, Height: 1080, X: 960, Y: 0}})
_, handled, err := c.PERFORM(TaskSaveLayout{Name: "coding"})
require.NoError(t, err)
assert.True(t, handled)
// Verify layout was saved with correct window states
layout, ok := svc.Manager().Layout().GetLayout("coding")
assert.True(t, ok)
assert.Equal(t, "coding", layout.Name)
assert.Len(t, layout.Windows, 2)
editorState, ok := layout.Windows["editor"]
assert.True(t, ok)
assert.Equal(t, 0, editorState.X)
assert.Equal(t, 960, editorState.Width)
termState, ok := layout.Windows["terminal"]
assert.True(t, ok)
assert.Equal(t, 960, termState.X)
assert.Equal(t, 960, termState.Width)
}
func TestTaskSaveLayout_Bad(t *testing.T) {
_, c := newTestWindowService(t)
// Saving an empty layout with empty name returns an error from LayoutManager
_, handled, err := c.PERFORM(TaskSaveLayout{Name: ""})
assert.True(t, handled)
assert.Error(t, err)
}
// --- TaskRestoreLayout ---
func TestTaskRestoreLayout_Good(t *testing.T) {
svc, c := newTestWindowService(t)
// Open windows
_, _, _ = c.PERFORM(TaskOpenWindow{Window: Window{Name: "editor", Width: 800, Height: 600, X: 0, Y: 0}})
_, _, _ = c.PERFORM(TaskOpenWindow{Window: Window{Name: "terminal", Width: 800, Height: 600, X: 0, Y: 0}})
// Save a layout with specific positions
_, _, _ = c.PERFORM(TaskSaveLayout{Name: "coding"})
// Move the windows to different positions
_, _, _ = c.PERFORM(TaskSetPosition{Name: "editor", X: 500, Y: 500})
_, _, _ = c.PERFORM(TaskSetPosition{Name: "terminal", X: 600, Y: 600})
// Restore the layout
_, handled, err := c.PERFORM(TaskRestoreLayout{Name: "coding"})
require.NoError(t, err)
assert.True(t, handled)
// Verify windows were moved back to saved positions
pw, ok := svc.Manager().Get("editor")
require.True(t, ok)
x, y := pw.Position()
assert.Equal(t, 0, x)
assert.Equal(t, 0, y)
pw2, ok := svc.Manager().Get("terminal")
require.True(t, ok)
x2, y2 := pw2.Position()
assert.Equal(t, 0, x2)
assert.Equal(t, 0, y2)
editorState, ok := svc.Manager().State().GetState("editor")
require.True(t, ok)
assert.Equal(t, 0, editorState.X)
assert.Equal(t, 0, editorState.Y)
terminalState, ok := svc.Manager().State().GetState("terminal")
require.True(t, ok)
assert.Equal(t, 0, terminalState.X)
assert.Equal(t, 0, terminalState.Y)
}
func TestTaskRestoreLayout_Bad(t *testing.T) {
_, c := newTestWindowService(t)
_, handled, err := c.PERFORM(TaskRestoreLayout{Name: "nonexistent"})
assert.True(t, handled)
assert.Error(t, err)
}
// --- TaskStackWindows ---
func TestTaskStackWindows_Good(t *testing.T) {
svc, c := newTestWindowService(t)
_, _, _ = c.PERFORM(TaskOpenWindow{Window: Window{Name: "s1", Width: 800, Height: 600}})
_, _, _ = c.PERFORM(TaskOpenWindow{Window: Window{Name: "s2", Width: 800, Height: 600}})
_, handled, err := c.PERFORM(TaskStackWindows{Windows: []string{"s1", "s2"}, OffsetX: 25, OffsetY: 35})
require.NoError(t, err)
assert.True(t, handled)
pw, ok := svc.Manager().Get("s2")
require.True(t, ok)
x, y := pw.Position()
assert.Equal(t, 25, x)
assert.Equal(t, 35, y)
}
// --- TaskApplyWorkflow ---
func TestTaskApplyWorkflow_Good(t *testing.T) {
svc, c := newTestWindowService(t)
_, _, _ = c.PERFORM(TaskOpenWindow{Window: Window{Name: "editor", Width: 800, Height: 600}})
_, _, _ = c.PERFORM(TaskOpenWindow{Window: Window{Name: "terminal", Width: 800, Height: 600}})
_, handled, err := c.PERFORM(TaskApplyWorkflow{Workflow: "side-by-side"})
require.NoError(t, err)
assert.True(t, handled)
editor, ok := svc.Manager().Get("editor")
require.True(t, ok)
x, y := editor.Position()
assert.Equal(t, 0, x)
assert.Equal(t, 0, y)
terminal, ok := svc.Manager().Get("terminal")
require.True(t, ok)
x, y = terminal.Position()
assert.Equal(t, 960, x)
assert.Equal(t, 0, y)
}