package display import ( "context" "encoding/base64" "os" "path/filepath" "testing" "forge.lthn.ai/core/go/pkg/core" "forge.lthn.ai/core/gui/pkg/clipboard" "forge.lthn.ai/core/gui/pkg/dialog" "forge.lthn.ai/core/gui/pkg/environment" "forge.lthn.ai/core/gui/pkg/menu" "forge.lthn.ai/core/gui/pkg/notification" "forge.lthn.ai/core/gui/pkg/screen" "forge.lthn.ai/core/gui/pkg/systray" "forge.lthn.ai/core/gui/pkg/webview" "forge.lthn.ai/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()), 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")) infos := svc.ListWindowInfos() assert.Len(t, infos, 2) } 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("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("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("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("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("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 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) }) }