gui/pkg/display/display_test.go
Snider 62ec735c10
Some checks failed
Security Scan / security (push) Has been cancelled
Test / test (push) Has been cancelled
refactor: AX compliance sweep — replace banned stdlib imports with core primitives
Replaced fmt, strings, sort, os, io, sync, encoding/json, path/filepath,
errors, log, reflect with core.Sprintf, core.E, core.Contains, core.Trim,
core.Split, core.Join, core.JoinPath, slices.Sort, c.Fs(), c.Lock(),
core.JSONMarshal, core.ReadAll and other CoreGO v0.8.0 primitives.

Framework boundary exceptions preserved where stdlib types are required
by external interfaces (Gin, net/http, CGo, Wails, bubbletea).

Co-Authored-By: Virgil <virgil@lethean.io>
2026-04-13 09:32:01 +01:00

1244 lines
36 KiB
Go

package display
import (
"context"
"encoding/base64"
"os"
"path/filepath"
"testing"
"forge.lthn.ai/core/go/pkg/core"
"dappco.re/go/core/gui/pkg/clipboard"
"dappco.re/go/core/gui/pkg/dialog"
"dappco.re/go/core/gui/pkg/environment"
"dappco.re/go/core/gui/pkg/menu"
"dappco.re/go/core/gui/pkg/notification"
"dappco.re/go/core/gui/pkg/screen"
"dappco.re/go/core/gui/pkg/systray"
"dappco.re/go/core/gui/pkg/webview"
"dappco.re/go/core/gui/pkg/window"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
type mockScreenPlatform struct {
screens []screen.Screen
}
func (m *mockScreenPlatform) GetAll() []screen.Screen { return m.screens }
func (m *mockScreenPlatform) GetPrimary() *screen.Screen {
for i := range m.screens {
if m.screens[i].IsPrimary {
return &m.screens[i]
}
}
return nil
}
type mockClipboardPlatform struct {
text string
ok bool
image []byte
imgOk bool
}
func (m *mockClipboardPlatform) Text() (string, bool) { return m.text, m.ok }
func (m *mockClipboardPlatform) SetText(text string) bool {
m.text = text
m.ok = text != ""
return true
}
func (m *mockClipboardPlatform) Image() ([]byte, bool) { return m.image, m.imgOk }
func (m *mockClipboardPlatform) SetImage(data []byte) bool {
m.image = data
m.imgOk = len(data) > 0
return true
}
type mockNotificationPlatform struct {
permGranted bool
sendCalled bool
clearCalled bool
lastOpts notification.NotificationOptions
}
func (m *mockNotificationPlatform) Send(opts notification.NotificationOptions) error {
m.sendCalled = true
m.lastOpts = opts
return nil
}
func (m *mockNotificationPlatform) SendWithActions(opts notification.NotificationOptions) error {
m.sendCalled = true
m.lastOpts = opts
return nil
}
func (m *mockNotificationPlatform) RequestPermission() (bool, error) { return m.permGranted, nil }
func (m *mockNotificationPlatform) CheckPermission() (bool, error) { return m.permGranted, nil }
func (m *mockNotificationPlatform) Clear() error {
m.clearCalled = true
return nil
}
type mockEnvironmentPlatform struct {
isDark bool
info environment.EnvironmentInfo
accent string
}
func (m *mockEnvironmentPlatform) IsDarkMode() bool { return m.isDark }
func (m *mockEnvironmentPlatform) Info() environment.EnvironmentInfo {
return m.info
}
func (m *mockEnvironmentPlatform) AccentColour() string { return m.accent }
func (m *mockEnvironmentPlatform) OpenFileManager(path string, selectFile bool) error {
return nil
}
func (m *mockEnvironmentPlatform) OnThemeChange(handler func(isDark bool)) func() {
return func() {}
}
func (m *mockEnvironmentPlatform) SetTheme(isDark bool) error {
m.isDark = isDark
return nil
}
type mockDialogPlatform struct {
button string
openFilePaths []string
saveFilePath string
openDirPath string
last dialog.MessageDialogOptions
}
func (m *mockDialogPlatform) OpenFile(opts dialog.OpenFileOptions) ([]string, error) {
if len(m.openFilePaths) == 0 {
return []string{"/tmp/file.txt"}, nil
}
return m.openFilePaths, nil
}
func (m *mockDialogPlatform) SaveFile(opts dialog.SaveFileOptions) (string, error) {
if m.saveFilePath == "" {
return "/tmp/save.txt", nil
}
return m.saveFilePath, nil
}
func (m *mockDialogPlatform) OpenDirectory(opts dialog.OpenDirectoryOptions) (string, error) {
if m.openDirPath == "" {
return "/tmp/dir", nil
}
return m.openDirPath, nil
}
func (m *mockDialogPlatform) MessageDialog(opts dialog.MessageDialogOptions) (string, error) {
m.last = opts
if m.button != "" {
return m.button, nil
}
return "OK", nil
}
type mockWebviewConnector struct{}
func (m *mockWebviewConnector) Navigate(url string) error { return nil }
func (m *mockWebviewConnector) Click(selector string) error { return nil }
func (m *mockWebviewConnector) Type(selector, text string) error { return nil }
func (m *mockWebviewConnector) Hover(selector string) error { return nil }
func (m *mockWebviewConnector) Select(selector, value string) error { return nil }
func (m *mockWebviewConnector) Check(selector string, checked bool) error { return nil }
func (m *mockWebviewConnector) Evaluate(script string) (any, error) { return nil, nil }
func (m *mockWebviewConnector) Screenshot() ([]byte, error) { return nil, nil }
func (m *mockWebviewConnector) GetURL() (string, error) { return "", nil }
func (m *mockWebviewConnector) GetTitle() (string, error) { return "", nil }
func (m *mockWebviewConnector) GetHTML(selector string) (string, error) { return "", nil }
func (m *mockWebviewConnector) QuerySelector(selector string) (*webview.ElementInfo, error) {
return nil, nil
}
func (m *mockWebviewConnector) QuerySelectorAll(selector string) ([]*webview.ElementInfo, error) {
return nil, nil
}
func (m *mockWebviewConnector) GetConsole() []webview.ConsoleMessage { return nil }
func (m *mockWebviewConnector) ClearConsole() {}
func (m *mockWebviewConnector) SetViewport(width, height int) error { return nil }
func (m *mockWebviewConnector) UploadFile(selector string, paths []string) error {
return nil
}
func (m *mockWebviewConnector) Close() error { return nil }
// --- Test helpers ---
// newTestDisplayService creates a display service registered with Core for IPC testing.
func newTestDisplayService(t *testing.T) (*Service, *core.Core) {
t.Helper()
c, err := core.New(
core.WithService(Register(nil)),
core.WithServiceLock(),
)
require.NoError(t, err)
require.NoError(t, c.ServiceStartup(context.Background(), nil))
svc := core.MustServiceFor[*Service](c, "display")
return svc, c
}
// newTestConclave creates a full 4-service conclave for integration testing.
func newTestConclave(t *testing.T) *core.Core {
t.Helper()
c, err := core.New(
core.WithService(Register(nil)),
core.WithService(window.Register(window.NewMockPlatform())),
core.WithService(screen.Register(&mockScreenPlatform{
screens: []screen.Screen{{
ID: "primary", Name: "Primary", IsPrimary: true,
Size: screen.Size{Width: 2560, Height: 1440},
Bounds: screen.Rect{X: 0, Y: 0, Width: 2560, Height: 1440},
WorkArea: screen.Rect{X: 0, Y: 0, Width: 2560, Height: 1440},
}},
})),
core.WithService(systray.Register(systray.NewMockPlatform())),
core.WithService(menu.Register(menu.NewMockPlatform())),
core.WithServiceLock(),
)
require.NoError(t, err)
require.NoError(t, c.ServiceStartup(context.Background(), nil))
return c
}
func newExtendedTestConclave(t *testing.T) *core.Core {
t.Helper()
fixture := newExtendedTestConclaveWithMocks(t)
return fixture.core
}
type extendedTestConclave struct {
core *core.Core
clipboardPlatform *mockClipboardPlatform
notificationPlatform *mockNotificationPlatform
dialogPlatform *mockDialogPlatform
environmentPlatform *mockEnvironmentPlatform
}
func newExtendedTestConclaveWithMocks(t *testing.T) *extendedTestConclave {
t.Helper()
clipboardPlatform := &mockClipboardPlatform{text: "hello", ok: true, image: []byte{1, 2, 3}, imgOk: true}
notificationPlatform := &mockNotificationPlatform{permGranted: true}
dialogPlatform := &mockDialogPlatform{button: "OK"}
environmentPlatform := &mockEnvironmentPlatform{
isDark: true,
accent: "rgb(0,122,255)",
info: environment.EnvironmentInfo{
OS: "darwin", Arch: "arm64",
Platform: environment.PlatformInfo{Name: "macOS", Version: "14.0"},
},
}
c, err := core.New(
core.WithService(Register(nil)),
core.WithService(window.Register(window.NewMockPlatform())),
core.WithService(screen.Register(&mockScreenPlatform{
screens: []screen.Screen{{
ID: "primary", Name: "Primary", IsPrimary: true,
Size: screen.Size{Width: 2560, Height: 1440},
Bounds: screen.Rect{X: 0, Y: 0, Width: 2560, Height: 1440},
WorkArea: screen.Rect{X: 0, Y: 0, Width: 2560, Height: 1440},
}},
})),
core.WithService(systray.Register(systray.NewMockPlatform())),
core.WithService(menu.Register(menu.NewMockPlatform())),
core.WithService(clipboard.Register(clipboardPlatform)),
core.WithService(notification.Register(notificationPlatform)),
core.WithService(dialog.Register(dialogPlatform)),
core.WithService(environment.Register(environmentPlatform)),
core.WithService(webview.Register(webview.Options{})),
core.WithServiceLock(),
)
require.NoError(t, err)
require.NoError(t, c.ServiceStartup(context.Background(), nil))
return &extendedTestConclave{
core: c,
clipboardPlatform: clipboardPlatform,
notificationPlatform: notificationPlatform,
dialogPlatform: dialogPlatform,
environmentPlatform: environmentPlatform,
}
}
// --- Tests ---
func TestNew(t *testing.T) {
t.Run("creates service successfully", func(t *testing.T) {
service, err := New()
assert.NoError(t, err)
assert.NotNil(t, service, "New() should return a non-nil service instance")
})
t.Run("returns independent instances", func(t *testing.T) {
service1, err1 := New()
service2, err2 := New()
assert.NoError(t, err1)
assert.NoError(t, err2)
assert.NotSame(t, service1, service2, "New() should return different instances")
})
}
func TestRegisterClosure_Good(t *testing.T) {
factory := Register(nil) // nil wailsApp for testing
assert.NotNil(t, factory)
c, err := core.New(
core.WithService(factory),
core.WithServiceLock(),
)
require.NoError(t, err)
require.NoError(t, c.ServiceStartup(context.Background(), nil))
svc := core.MustServiceFor[*Service](c, "display")
assert.NotNil(t, svc)
}
func TestConfigQuery_Good(t *testing.T) {
svc, c := newTestDisplayService(t)
// Set window config
svc.configData["window"] = map[string]any{
"default_width": 1024,
}
result, handled, err := c.QUERY(window.QueryConfig{})
require.NoError(t, err)
assert.True(t, handled)
cfg := result.(map[string]any)
assert.Equal(t, 1024, cfg["default_width"])
}
func TestConfigQuery_Bad(t *testing.T) {
// No display service — window config query returns handled=false
c, err := core.New(core.WithServiceLock())
require.NoError(t, err)
_, handled, _ := c.QUERY(window.QueryConfig{})
assert.False(t, handled)
}
func TestConfigTask_Good(t *testing.T) {
_, c := newTestDisplayService(t)
newCfg := map[string]any{"default_width": 800}
_, handled, err := c.PERFORM(window.TaskSaveConfig{Value: newCfg})
require.NoError(t, err)
assert.True(t, handled)
// Verify config was saved
result, _, _ := c.QUERY(window.QueryConfig{})
cfg := result.(map[string]any)
assert.Equal(t, 800, cfg["default_width"])
}
// --- Conclave integration tests ---
func TestServiceConclave_Good(t *testing.T) {
c := newTestConclave(t)
// Open a window via IPC
result, handled, err := c.PERFORM(window.TaskOpenWindow{
Opts: []window.WindowOption{window.WithName("main")},
})
require.NoError(t, err)
assert.True(t, handled)
info := result.(window.WindowInfo)
assert.Equal(t, "main", info.Name)
// Query window config from display
val, handled, err := c.QUERY(window.QueryConfig{})
require.NoError(t, err)
assert.True(t, handled)
assert.NotNil(t, val)
// Set app menu via IPC
_, handled, err = c.PERFORM(menu.TaskSetAppMenu{Items: []menu.MenuItem{
{Label: "File"},
}})
require.NoError(t, err)
assert.True(t, handled)
// Query app menu via IPC
menuResult, handled, _ := c.QUERY(menu.QueryGetAppMenu{})
assert.True(t, handled)
items := menuResult.([]menu.MenuItem)
assert.Len(t, items, 1)
}
func TestServiceConclave_Bad(t *testing.T) {
// Sub-service starts without display — config QUERY returns handled=false
c, err := core.New(
core.WithService(window.Register(window.NewMockPlatform())),
core.WithServiceLock(),
)
require.NoError(t, err)
require.NoError(t, c.ServiceStartup(context.Background(), nil))
_, handled, _ := c.QUERY(window.QueryConfig{})
assert.False(t, handled, "no display service means no config handler")
}
// --- IPC delegation tests (full conclave) ---
func TestOpenWindow_Good(t *testing.T) {
c := newTestConclave(t)
svc := core.MustServiceFor[*Service](c, "display")
t.Run("creates window with default options", func(t *testing.T) {
err := svc.OpenWindow()
assert.NoError(t, err)
// Verify via IPC query
infos := svc.ListWindowInfos()
assert.GreaterOrEqual(t, len(infos), 1)
})
t.Run("creates window with custom options", func(t *testing.T) {
err := svc.OpenWindow(
window.WithName("custom-window"),
window.WithTitle("Custom Title"),
window.WithSize(640, 480),
window.WithURL("/custom"),
)
assert.NoError(t, err)
result, _, _ := c.QUERY(window.QueryWindowByName{Name: "custom-window"})
info := result.(*window.WindowInfo)
assert.Equal(t, "custom-window", info.Name)
})
}
func TestGetWindowInfo_Good(t *testing.T) {
c := newTestConclave(t)
svc := core.MustServiceFor[*Service](c, "display")
_ = svc.OpenWindow(
window.WithName("test-win"),
window.WithSize(800, 600),
)
// Modify position via IPC
_, _, _ = c.PERFORM(window.TaskSetPosition{Name: "test-win", X: 100, Y: 200})
info, err := svc.GetWindowInfo("test-win")
require.NoError(t, err)
assert.Equal(t, "test-win", info.Name)
assert.Equal(t, 100, info.X)
assert.Equal(t, 200, info.Y)
assert.Equal(t, 800, info.Width)
assert.Equal(t, 600, info.Height)
assert.True(t, info.Visible)
assert.False(t, info.Minimized)
}
func TestGetWindowInfo_Bad(t *testing.T) {
c := newTestConclave(t)
svc := core.MustServiceFor[*Service](c, "display")
info, err := svc.GetWindowInfo("nonexistent")
// QueryWindowByName returns nil for nonexistent — handled=true, result=nil
assert.NoError(t, err)
assert.Nil(t, info)
}
func TestTileWindows_UsesPrimaryScreenSize(t *testing.T) {
c := newTestConclave(t)
svc := core.MustServiceFor[*Service](c, "display")
_ = svc.OpenWindow(window.WithName("left"))
_ = svc.OpenWindow(window.WithName("right"))
err := svc.TileWindows(window.TileModeLeftRight, []string{"left", "right"})
require.NoError(t, err)
left, err := svc.GetWindowInfo("left")
require.NoError(t, err)
assert.Equal(t, 1280, left.Width)
right, err := svc.GetWindowInfo("right")
require.NoError(t, err)
assert.Equal(t, 1280, right.Width)
}
func TestListWindowInfos_Good(t *testing.T) {
c := newTestConclave(t)
svc := core.MustServiceFor[*Service](c, "display")
_ = svc.OpenWindow(window.WithName("win-1"))
_ = svc.OpenWindow(window.WithName("win-2"))
_ = svc.MinimizeWindow("win-2")
infos := svc.ListWindowInfos()
assert.Len(t, infos, 2)
byName := make(map[string]window.WindowInfo, len(infos))
for _, info := range infos {
byName[info.Name] = info
}
assert.True(t, byName["win-1"].Visible)
assert.False(t, byName["win-1"].Minimized)
assert.False(t, byName["win-2"].Visible)
assert.True(t, byName["win-2"].Minimized)
}
func TestSetWindowPosition_Good(t *testing.T) {
c := newTestConclave(t)
svc := core.MustServiceFor[*Service](c, "display")
_ = svc.OpenWindow(window.WithName("pos-win"))
err := svc.SetWindowPosition("pos-win", 300, 400)
assert.NoError(t, err)
info, _ := svc.GetWindowInfo("pos-win")
assert.Equal(t, 300, info.X)
assert.Equal(t, 400, info.Y)
}
func TestSetWindowPosition_Bad(t *testing.T) {
c := newTestConclave(t)
svc := core.MustServiceFor[*Service](c, "display")
err := svc.SetWindowPosition("nonexistent", 0, 0)
assert.Error(t, err)
}
func TestSetWindowSize_Good(t *testing.T) {
c := newTestConclave(t)
svc := core.MustServiceFor[*Service](c, "display")
_ = svc.OpenWindow(window.WithName("size-win"))
err := svc.SetWindowSize("size-win", 1024, 768)
assert.NoError(t, err)
info, _ := svc.GetWindowInfo("size-win")
assert.Equal(t, 1024, info.Width)
assert.Equal(t, 768, info.Height)
}
func TestMaximizeWindow_Good(t *testing.T) {
c := newTestConclave(t)
svc := core.MustServiceFor[*Service](c, "display")
_ = svc.OpenWindow(window.WithName("max-win"))
err := svc.MaximizeWindow("max-win")
assert.NoError(t, err)
info, _ := svc.GetWindowInfo("max-win")
assert.True(t, info.Maximized)
}
func TestRestoreWindow_Good(t *testing.T) {
c := newTestConclave(t)
svc := core.MustServiceFor[*Service](c, "display")
_ = svc.OpenWindow(window.WithName("restore-win"))
_ = svc.MaximizeWindow("restore-win")
err := svc.RestoreWindow("restore-win")
assert.NoError(t, err)
info, _ := svc.GetWindowInfo("restore-win")
assert.False(t, info.Maximized)
}
func TestFocusWindow_Good(t *testing.T) {
c := newTestConclave(t)
svc := core.MustServiceFor[*Service](c, "display")
_ = svc.OpenWindow(window.WithName("focus-win"))
err := svc.FocusWindow("focus-win")
assert.NoError(t, err)
info, _ := svc.GetWindowInfo("focus-win")
assert.True(t, info.Focused)
}
func TestCloseWindow_Good(t *testing.T) {
c := newTestConclave(t)
svc := core.MustServiceFor[*Service](c, "display")
_ = svc.OpenWindow(window.WithName("close-win"))
err := svc.CloseWindow("close-win")
assert.NoError(t, err)
// Window should be removed
info, _ := svc.GetWindowInfo("close-win")
assert.Nil(t, info)
}
func TestSetWindowVisibility_Good(t *testing.T) {
c := newTestConclave(t)
svc := core.MustServiceFor[*Service](c, "display")
_ = svc.OpenWindow(window.WithName("vis-win"))
err := svc.SetWindowVisibility("vis-win", false)
assert.NoError(t, err)
info, err := svc.GetWindowInfo("vis-win")
require.NoError(t, err)
assert.False(t, info.Visible)
err = svc.SetWindowVisibility("vis-win", true)
assert.NoError(t, err)
info, err = svc.GetWindowInfo("vis-win")
require.NoError(t, err)
assert.True(t, info.Visible)
}
func TestSetWindowAlwaysOnTop_Good(t *testing.T) {
c := newTestConclave(t)
svc := core.MustServiceFor[*Service](c, "display")
_ = svc.OpenWindow(window.WithName("ontop-win"))
err := svc.SetWindowAlwaysOnTop("ontop-win", true)
assert.NoError(t, err)
}
func TestSetWindowTitle_Good(t *testing.T) {
c := newTestConclave(t)
svc := core.MustServiceFor[*Service](c, "display")
_ = svc.OpenWindow(window.WithName("title-win"))
err := svc.SetWindowTitle("title-win", "New Title")
assert.NoError(t, err)
}
func TestGetFocusedWindow_Good(t *testing.T) {
c := newTestConclave(t)
svc := core.MustServiceFor[*Service](c, "display")
_ = svc.OpenWindow(window.WithName("win-a"))
_ = svc.OpenWindow(window.WithName("win-b"))
_ = svc.FocusWindow("win-b")
focused := svc.GetFocusedWindow()
assert.Equal(t, "win-b", focused)
}
func TestGetFocusedWindow_NoneSelected(t *testing.T) {
c := newTestConclave(t)
svc := core.MustServiceFor[*Service](c, "display")
_ = svc.OpenWindow(window.WithName("win-a"))
focused := svc.GetFocusedWindow()
assert.Equal(t, "", focused)
}
func TestCreateWindow_Good(t *testing.T) {
c := newTestConclave(t)
svc := core.MustServiceFor[*Service](c, "display")
info, err := svc.CreateWindow(CreateWindowOptions{
Name: "new-win",
Title: "New Window",
URL: "/new",
Width: 600,
Height: 400,
})
require.NoError(t, err)
assert.Equal(t, "new-win", info.Name)
}
func TestCreateWindow_Bad(t *testing.T) {
c := newTestConclave(t)
svc := core.MustServiceFor[*Service](c, "display")
_, err := svc.CreateWindow(CreateWindowOptions{})
assert.Error(t, err)
assert.Contains(t, err.Error(), "window name is required")
}
func TestResetWindowState_Good(t *testing.T) {
c := newTestConclave(t)
svc := core.MustServiceFor[*Service](c, "display")
err := svc.ResetWindowState()
assert.NoError(t, err)
}
func TestGetSavedWindowStates_Good(t *testing.T) {
c := newTestConclave(t)
svc := core.MustServiceFor[*Service](c, "display")
states := svc.GetSavedWindowStates()
assert.NotNil(t, states)
}
func TestServiceWrappers_Good(t *testing.T) {
fixture := newExtendedTestConclaveWithMocks(t)
svc := core.MustServiceFor[*Service](fixture.core, "display")
t.Run("screen wrappers", func(t *testing.T) {
screens := svc.GetScreens()
require.Len(t, screens, 1)
primary, err := svc.GetPrimaryScreen()
require.NoError(t, err)
require.NotNil(t, primary)
assert.True(t, primary.IsPrimary)
atPoint, err := svc.GetScreenAtPoint(10, 10)
require.NoError(t, err)
require.NotNil(t, atPoint)
workAreas := svc.GetWorkAreas()
require.Len(t, workAreas, 1)
})
t.Run("window-screen lookup", func(t *testing.T) {
require.NoError(t, svc.OpenWindow(window.WithName("screen-win"), window.WithSize(640, 480)))
screenInfo, err := svc.GetScreenForWindow("screen-win")
require.NoError(t, err)
require.NotNil(t, screenInfo)
})
t.Run("layout helpers", func(t *testing.T) {
suggestion, err := svc.SuggestLayout(2, 2560, 1440)
require.NoError(t, err)
assert.Equal(t, "side-by-side", suggestion.Mode)
require.NoError(t, svc.OpenWindow(window.WithName("editor-beside"), window.WithSize(800, 600)))
require.NoError(t, svc.OpenWindow(window.WithName("assistant-beside"), window.WithSize(400, 600)))
require.NoError(t, svc.BesideEditor("editor-beside", "assistant-beside"))
editorInfo, err := svc.GetWindowInfo("editor-beside")
require.NoError(t, err)
require.NotNil(t, editorInfo)
assert.Equal(t, 0, editorInfo.X)
assert.Equal(t, 1792, editorInfo.Width)
assistantInfo, err := svc.GetWindowInfo("assistant-beside")
require.NoError(t, err)
require.NotNil(t, assistantInfo)
assert.Equal(t, 1792, assistantInfo.X)
assert.Equal(t, 768, assistantInfo.Width)
})
t.Run("clipboard wrappers", func(t *testing.T) {
text, err := svc.ReadClipboard()
require.NoError(t, err)
assert.Equal(t, "hello", text)
assert.True(t, svc.HasClipboard())
require.NoError(t, svc.WriteClipboard("updated"))
text, err = svc.ReadClipboard()
require.NoError(t, err)
assert.Equal(t, "updated", text)
image, err := svc.ReadClipboardImage()
require.NoError(t, err)
assert.True(t, image.HasContent)
require.NoError(t, svc.WriteClipboardImage([]byte{9, 8, 7}))
require.NoError(t, svc.ClearClipboard())
assert.False(t, svc.HasClipboard())
})
t.Run("notification wrappers", func(t *testing.T) {
require.NoError(t, svc.ShowInfoNotification("Info", "Hello"))
require.True(t, fixture.notificationPlatform.sendCalled)
assert.Equal(t, notification.SeverityInfo, fixture.notificationPlatform.lastOpts.Severity)
granted, err := svc.RequestNotificationPermission()
require.NoError(t, err)
assert.True(t, granted)
granted, err = svc.CheckNotificationPermission()
require.NoError(t, err)
assert.True(t, granted)
require.NoError(t, svc.ClearNotifications())
assert.True(t, fixture.notificationPlatform.clearCalled)
})
t.Run("compatibility aliases", func(t *testing.T) {
_ = svc.OpenWindow(window.WithName("alias-win"))
require.NoError(t, svc.FocusSet("alias-win"))
info, err := svc.GetWindowInfo("alias-win")
require.NoError(t, err)
require.NotNil(t, info)
assert.True(t, info.Focused)
require.NoError(t, svc.DialogMessage("warning", "Alias", "Message"))
assert.Equal(t, notification.SeverityWarning, fixture.notificationPlatform.lastOpts.Severity)
assert.Equal(t, "Alias", fixture.notificationPlatform.lastOpts.Title)
assert.Equal(t, "Message", fixture.notificationPlatform.lastOpts.Message)
})
t.Run("dialog wrappers", func(t *testing.T) {
paths, err := svc.OpenFileDialog(dialog.OpenFileOptions{Title: "Pick"})
require.NoError(t, err)
require.NotEmpty(t, paths)
path, err := svc.OpenSingleFileDialog(dialog.OpenFileOptions{Title: "Pick"})
require.NoError(t, err)
assert.Equal(t, paths[0], path)
path, err = svc.SaveFileDialog(dialog.SaveFileOptions{Filename: "out.txt"})
require.NoError(t, err)
assert.Equal(t, "/tmp/save.txt", path)
path, err = svc.OpenDirectoryDialog(dialog.OpenDirectoryOptions{Title: "Pick Dir"})
require.NoError(t, err)
assert.Equal(t, "/tmp/dir", path)
confirmed, err := svc.ConfirmDialog("Confirm", "Continue?")
require.NoError(t, err)
assert.True(t, confirmed)
button, accepted, err := svc.PromptDialog("Question", "Continue?")
require.NoError(t, err)
assert.Equal(t, "OK", button)
assert.True(t, accepted)
})
t.Run("theme wrappers", func(t *testing.T) {
theme := svc.GetTheme()
require.NotNil(t, theme)
assert.True(t, theme.IsDark)
assert.Equal(t, "dark", svc.GetSystemTheme())
require.NoError(t, svc.SetTheme(false))
assert.False(t, fixture.environmentPlatform.isDark)
theme = svc.GetTheme()
require.NotNil(t, theme)
assert.False(t, theme.IsDark)
assert.Equal(t, "light", svc.GetSystemTheme())
})
t.Run("tray wrappers", func(t *testing.T) {
info := svc.GetTrayInfo()
require.NotNil(t, info)
assert.True(t, info["active"].(bool))
require.NoError(t, svc.SetTrayTooltip("Updated Tooltip"))
require.NoError(t, svc.SetTrayLabel("Updated Label"))
require.NoError(t, svc.SetTrayIcon([]byte{1, 2, 3}))
require.NoError(t, svc.SetTrayMenu([]systray.TrayMenuItem{
{Label: "One", ActionID: "one"},
{Type: "separator"},
{Label: "More", Submenu: []systray.TrayMenuItem{{Label: "Two", ActionID: "two"}}},
}))
info = svc.GetTrayInfo()
require.NotNil(t, info)
assert.Equal(t, "Updated Tooltip", info["tooltip"])
assert.Equal(t, "Updated Label", info["label"])
assert.True(t, info["hasIcon"].(bool))
items, ok := info["menuItems"].([]systray.TrayMenuItem)
require.True(t, ok)
require.Len(t, items, 3)
})
}
func TestHandleIPCEvents_WindowOpened_Good(t *testing.T) {
c := newTestConclave(t)
// Open a window — this should trigger ActionWindowOpened
// which HandleIPCEvents should convert to a WS event
result, handled, err := c.PERFORM(window.TaskOpenWindow{
Opts: []window.WindowOption{window.WithName("test")},
})
require.NoError(t, err)
assert.True(t, handled)
info := result.(window.WindowInfo)
assert.Equal(t, "test", info.Name)
}
func TestHandleListWorkspaces_Good(t *testing.T) {
c := newTestConclave(t)
svc := core.MustServiceFor[*Service](c, "display")
// handleListWorkspaces should not panic when workspace service is not available
assert.NotPanics(t, func() {
svc.handleListWorkspaces()
})
}
func TestWSEventManager_Good(t *testing.T) {
em := NewWSEventManager()
defer em.Close()
assert.NotNil(t, em)
assert.Equal(t, 0, em.ConnectedClients())
}
// --- Config file loading tests ---
func TestLoadConfig_Good(t *testing.T) {
// Create temp config file
dir := t.TempDir()
cfgPath := filepath.Join(dir, ".core", "gui", "config.yaml")
require.NoError(t, os.MkdirAll(filepath.Dir(cfgPath), 0o755))
require.NoError(t, os.WriteFile(cfgPath, []byte(`
window:
default_width: 1280
default_height: 720
systray:
tooltip: "Test App"
menu:
show_dev_tools: false
`), 0o644))
s, _ := New()
s.loadConfigFrom(cfgPath)
// Verify configData was populated from file
assert.Equal(t, 1280, s.configData["window"]["default_width"])
assert.Equal(t, "Test App", s.configData["systray"]["tooltip"])
assert.Equal(t, false, s.configData["menu"]["show_dev_tools"])
}
func TestLoadConfig_Bad_MissingFile(t *testing.T) {
s, _ := New()
s.loadConfigFrom(filepath.Join(t.TempDir(), "nonexistent.yaml"))
// Should not panic, configData stays at empty defaults
assert.Empty(t, s.configData["window"])
assert.Empty(t, s.configData["systray"])
assert.Empty(t, s.configData["menu"])
}
func TestHandleConfigTask_Persists_Good(t *testing.T) {
dir := t.TempDir()
cfgPath := filepath.Join(dir, "config.yaml")
s, _ := New()
s.loadConfigFrom(cfgPath) // Creates empty config (file doesn't exist yet)
// Simulate a TaskSaveConfig through the handler
c, _ := core.New(
core.WithService(func(c *core.Core) (any, error) {
s.ServiceRuntime = core.NewServiceRuntime[Options](c, Options{})
return s, nil
}),
core.WithServiceLock(),
)
c.ServiceStartup(context.Background(), nil)
_, handled, err := c.PERFORM(window.TaskSaveConfig{
Value: map[string]any{"default_width": 1920},
})
require.NoError(t, err)
assert.True(t, handled)
// Verify file was written
data, err := os.ReadFile(cfgPath)
require.NoError(t, err)
assert.Contains(t, string(data), "default_width")
}
func TestHandleWSMessage_Extended_Good(t *testing.T) {
c := newExtendedTestConclave(t)
svc := core.MustServiceFor[*Service](c, "display")
_ = svc.OpenWindow(
window.WithName("editor"),
window.WithTitle("Editor"),
window.WithSize(1200, 800),
)
_ = svc.OpenWindow(
window.WithName("assistant"),
window.WithTitle("Assistant"),
window.WithSize(900, 800),
)
t.Run("layout suggest", func(t *testing.T) {
result, handled, err := svc.handleWSMessage(WSMessage{Action: "layout:suggest"})
require.NoError(t, err)
assert.True(t, handled)
suggestion, ok := result.(window.LayoutSuggestion)
require.True(t, ok)
assert.Equal(t, "side-by-side", suggestion.Mode)
})
t.Run("window list", func(t *testing.T) {
result, handled, err := svc.handleWSMessage(WSMessage{Action: "window:list"})
require.NoError(t, err)
assert.True(t, handled)
windows, ok := result.([]window.WindowInfo)
require.True(t, ok)
assert.GreaterOrEqual(t, len(windows), 2)
})
t.Run("window get", func(t *testing.T) {
result, handled, err := svc.handleWSMessage(WSMessage{
Action: "window:get",
Data: map[string]any{"name": "editor"},
})
require.NoError(t, err)
assert.True(t, handled)
info, ok := result.(*window.WindowInfo)
require.True(t, ok)
require.NotNil(t, info)
assert.Equal(t, "editor", info.Name)
})
t.Run("window focused", func(t *testing.T) {
require.NoError(t, svc.FocusWindow("assistant"))
result, handled, err := svc.handleWSMessage(WSMessage{Action: "window:focused"})
require.NoError(t, err)
assert.True(t, handled)
assert.Equal(t, "assistant", result)
})
t.Run("window title get", func(t *testing.T) {
result, handled, err := svc.handleWSMessage(WSMessage{
Action: "window:title-get",
Data: map[string]any{"name": "editor"},
})
require.NoError(t, err)
assert.True(t, handled)
assert.Equal(t, "Editor", result)
})
t.Run("window position and bounds", func(t *testing.T) {
_, handled, err := svc.handleWSMessage(WSMessage{
Action: "window:position",
Data: map[string]any{
"name": "assistant",
"x": float64(40),
"y": float64(50),
},
})
require.NoError(t, err)
assert.True(t, handled)
_, handled, err = svc.handleWSMessage(WSMessage{
Action: "window:bounds",
Data: map[string]any{
"name": "assistant",
"x": float64(60),
"y": float64(70),
"width": float64(800),
"height": float64(640),
},
})
require.NoError(t, err)
assert.True(t, handled)
info, err := svc.GetWindowInfo("assistant")
require.NoError(t, err)
require.NotNil(t, info)
assert.Equal(t, 60, info.X)
assert.Equal(t, 70, info.Y)
assert.Equal(t, 800, info.Width)
assert.Equal(t, 640, info.Height)
})
t.Run("window create and close", func(t *testing.T) {
result, handled, err := svc.handleWSMessage(WSMessage{
Action: "window:create",
Data: map[string]any{
"name": "ws-new",
"title": "WS New",
"url": "/ws-new",
"width": float64(500),
"height": float64(350),
},
})
require.NoError(t, err)
assert.True(t, handled)
created, ok := result.(*window.WindowInfo)
require.True(t, ok)
require.NotNil(t, created)
assert.Equal(t, "ws-new", created.Name)
_, handled, err = svc.handleWSMessage(WSMessage{
Action: "window:close",
Data: map[string]any{"name": "ws-new"},
})
require.NoError(t, err)
assert.True(t, handled)
})
t.Run("layout stack", func(t *testing.T) {
_, handled, err := svc.handleWSMessage(WSMessage{
Action: "layout:stack",
Data: map[string]any{
"windows": []any{"editor", "assistant"},
"offsetX": 25,
"offsetY": 30,
},
})
require.NoError(t, err)
assert.True(t, handled)
})
t.Run("layout workflow", func(t *testing.T) {
_, handled, err := svc.handleWSMessage(WSMessage{
Action: "layout:workflow",
Data: map[string]any{
"workflow": "coding",
"windows": []any{"editor", "assistant"},
},
})
require.NoError(t, err)
assert.True(t, handled)
})
t.Run("window arrange pair", func(t *testing.T) {
_, handled, err := svc.handleWSMessage(WSMessage{
Action: "window:arrange-pair",
Data: map[string]any{
"first": "editor",
"second": "assistant",
},
})
require.NoError(t, err)
assert.True(t, handled)
})
t.Run("screen find space", func(t *testing.T) {
result, handled, err := svc.handleWSMessage(WSMessage{
Action: "screen:find-space",
Data: map[string]any{
"width": float64(400),
"height": float64(300),
},
})
require.NoError(t, err)
assert.True(t, handled)
space, ok := result.(window.SpaceInfo)
require.True(t, ok)
assert.Equal(t, 2560, space.ScreenWidth)
assert.Equal(t, 1440, space.ScreenHeight)
assert.Equal(t, 400, space.Width)
assert.Equal(t, 300, space.Height)
})
t.Run("clipboard image read", func(t *testing.T) {
result, handled, err := svc.handleWSMessage(WSMessage{Action: "clipboard:read-image"})
require.NoError(t, err)
assert.True(t, handled)
content, ok := result.(clipboard.ClipboardImageContent)
require.True(t, ok)
assert.True(t, content.HasContent)
assert.NotEmpty(t, content.Base64)
})
t.Run("clipboard image write", func(t *testing.T) {
payload := base64.StdEncoding.EncodeToString([]byte{9, 8, 7})
_, handled, err := svc.handleWSMessage(WSMessage{
Action: "clipboard:write-image",
Data: map[string]any{"data": payload},
})
require.NoError(t, err)
assert.True(t, handled)
})
t.Run("notification actions", func(t *testing.T) {
_, handled, err := svc.handleWSMessage(WSMessage{
Action: "notification:with-actions",
Data: map[string]any{
"title": "Heads up",
"message": "Choose one",
"actions": []any{
map[string]any{"id": "ok", "label": "OK"},
},
},
})
require.NoError(t, err)
assert.True(t, handled)
})
t.Run("notification clear", func(t *testing.T) {
_, handled, err := svc.handleWSMessage(WSMessage{Action: "notification:clear"})
require.NoError(t, err)
assert.True(t, handled)
})
t.Run("theme set", func(t *testing.T) {
_, handled, err := svc.handleWSMessage(WSMessage{
Action: "theme:set",
Data: map[string]any{"theme": "light"},
})
require.NoError(t, err)
assert.True(t, handled)
theme := svc.GetTheme()
require.NotNil(t, theme)
assert.False(t, theme.IsDark)
})
t.Run("webview devtools", func(t *testing.T) {
_, handled, err := svc.handleWSMessage(WSMessage{
Action: "webview:devtools-open",
Data: map[string]any{"window": "editor"},
})
require.NoError(t, err)
assert.True(t, handled)
_, handled, err = svc.handleWSMessage(WSMessage{
Action: "webview:devtools-close",
Data: map[string]any{"window": "editor"},
})
require.NoError(t, err)
assert.True(t, handled)
})
t.Run("webview errors", func(t *testing.T) {
result, handled, err := svc.handleWSMessage(WSMessage{
Action: "webview:errors",
Data: map[string]any{"window": "editor", "limit": float64(10)},
})
require.NoError(t, err)
assert.True(t, handled)
errors, ok := result.([]webview.ExceptionInfo)
require.True(t, ok)
assert.Len(t, errors, 0)
})
t.Run("tray message", func(t *testing.T) {
_, handled, err := svc.handleWSMessage(WSMessage{
Action: "tray:show-message",
Data: map[string]any{"title": "Core", "message": "Ready"},
})
require.NoError(t, err)
assert.True(t, handled)
})
t.Run("tray close desktop", func(t *testing.T) {
svc.handleTrayAction("close-desktop")
for _, info := range svc.ListWindowInfos() {
assert.False(t, info.Visible, "window should be hidden after close-desktop")
}
})
t.Run("tray tooltip", func(t *testing.T) {
_, handled, err := svc.handleWSMessage(WSMessage{
Action: "tray:set-tooltip",
Data: map[string]any{"tooltip": "Updated"},
})
require.NoError(t, err)
assert.True(t, handled)
})
t.Run("tray label", func(t *testing.T) {
_, handled, err := svc.handleWSMessage(WSMessage{
Action: "tray:set-label",
Data: map[string]any{"label": "Updated"},
})
require.NoError(t, err)
assert.True(t, handled)
})
t.Run("prompt dialog", func(t *testing.T) {
result, handled, err := svc.handleWSMessage(WSMessage{
Action: "dialog:prompt",
Data: map[string]any{"title": "Question", "message": "Continue?"},
})
require.NoError(t, err)
assert.True(t, handled)
assert.Equal(t, "OK", result)
})
t.Run("event info", func(t *testing.T) {
result, handled, err := svc.handleWSMessage(WSMessage{Action: "event:info"})
require.NoError(t, err)
assert.True(t, handled)
info, ok := result.(EventServerInfo)
require.True(t, ok)
assert.Equal(t, 0, info.ConnectedClients)
assert.Equal(t, 0, info.Subscriptions)
})
}