From 3bcca95b5e426a0a456c01ee0d42bad088fe7cc4 Mon Sep 17 00:00:00 2001 From: Virgil Date: Tue, 31 Mar 2026 13:19:46 +0000 Subject: [PATCH] refactor(ax): align GUI code with declarative AX principles Co-Authored-By: Virgil --- pkg/display/display.go | 15 ++++-- pkg/display/display_test.go | 76 ++++++++++++++++--------------- pkg/mcp/mcp_test.go | 6 +-- pkg/mcp/subsystem.go | 11 +++-- pkg/mcp/tools_clipboard.go | 1 - pkg/mcp/tools_contextmenu.go | 1 - pkg/mcp/tools_environment.go | 1 - pkg/menu/menu.go | 3 ++ pkg/systray/tray.go | 2 + pkg/webview/service.go | 49 +++++++++++++++----- pkg/webview/service_test.go | 52 ++++++++++++++------- pkg/window/messages.go | 7 ++- pkg/window/options.go | 6 ++- pkg/window/service.go | 11 ++--- pkg/window/service_screen_test.go | 15 ++---- pkg/window/service_test.go | 70 ++++++++++++++-------------- pkg/window/window.go | 62 ++++++++++++++++--------- pkg/window/window_test.go | 70 ++++++++++++++-------------- 18 files changed, 267 insertions(+), 191 deletions(-) diff --git a/pkg/display/display.go b/pkg/display/display.go index f6cca53..c4b51a9 100644 --- a/pkg/display/display.go +++ b/pkg/display/display.go @@ -43,9 +43,9 @@ type Service struct { events *WSEventManager } -// New returns a display Service with empty config sections. -// s, _ := display.New(); s.loadConfigFrom("/path/to/config.yaml") -func New() (*Service, error) { +// NewService returns a display Service with empty config sections. +// svc, _ := display.NewService(); _, _ = svc.CreateWindow(display.CreateWindowOptions{Name: "settings", URL: "/settings", Width: 800, Height: 600}) +func NewService() (*Service, error) { return &Service{ configData: map[string]map[string]any{ "window": {}, @@ -55,12 +55,17 @@ func New() (*Service, error) { }, nil } +// Deprecated: use NewService(). +func New() (*Service, error) { + return NewService() +} + // Register binds the display service to a Core instance. // core.WithService(display.Register(app)) // production (Wails app) // core.WithService(display.Register(nil)) // tests (no Wails runtime) func Register(wailsApp *application.App) func(*core.Core) (any, error) { return func(c *core.Core) (any, error) { - s, err := New() + s, err := NewService() if err != nil { return nil, err } @@ -584,7 +589,7 @@ func (s *Service) windowService() *window.Service { // --- Window Management (delegates via IPC) --- -// OpenWindow creates a new window via IPC. +// Deprecated: use CreateWindow(display.CreateWindowOptions{Name: "settings", URL: "/settings", Width: 800, Height: 600}). func (s *Service) OpenWindow(options ...window.WindowOption) error { spec, err := window.ApplyOptions(options...) if err != nil { diff --git a/pkg/display/display_test.go b/pkg/display/display_test.go index 4b10798..ebc7a8a 100644 --- a/pkg/display/display_test.go +++ b/pkg/display/display_test.go @@ -44,17 +44,23 @@ func newTestConclave(t *testing.T) *core.Core { return c } +func requireCreateWindow(t *testing.T, svc *Service, options CreateWindowOptions) { + t.Helper() + _, err := svc.CreateWindow(options) + require.NoError(t, err) +} + // --- Tests --- -func TestNew_Good(t *testing.T) { - service, err := New() +func TestNewService_Good(t *testing.T) { + service, err := NewService() assert.NoError(t, err) assert.NotNil(t, service) } -func TestNew_Good_IndependentInstances(t *testing.T) { - service1, err1 := New() - service2, err2 := New() +func TestNewService_Good_IndependentInstances(t *testing.T) { + service1, err1 := NewService() + service2, err2 := NewService() assert.NoError(t, err1) assert.NoError(t, err2) assert.NotSame(t, service1, service2) @@ -161,7 +167,7 @@ func TestServiceConclave_Bad(t *testing.T) { // --- IPC delegation tests (full conclave) --- -func TestOpenWindow_Good(t *testing.T) { +func TestOpenWindow_Compatibility_Good(t *testing.T) { c := newTestConclave(t) svc := core.MustServiceFor[*Service](c, "display") @@ -174,17 +180,16 @@ func TestOpenWindow_Good(t *testing.T) { 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) + t.Run("creates window with declarative options", func(t *testing.T) { + info, err := svc.CreateWindow(CreateWindowOptions{ + Name: "custom-window", + Title: "Custom Title", + URL: "/custom", + Width: 640, + Height: 480, + }) + require.NoError(t, err) - result, _, _ := c.QUERY(window.QueryWindowByName{Name: "custom-window"}) - info := result.(*window.WindowInfo) assert.Equal(t, "custom-window", info.Name) }) } @@ -193,10 +198,7 @@ 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), - ) + requireCreateWindow(t, svc, CreateWindowOptions{Name: "test-win", Width: 800, Height: 600}) // Modify position via IPC _, _, _ = c.PERFORM(window.TaskSetPosition{Name: "test-win", X: 100, Y: 200}) @@ -224,8 +226,8 @@ 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")) + requireCreateWindow(t, svc, CreateWindowOptions{Name: "win-1"}) + requireCreateWindow(t, svc, CreateWindowOptions{Name: "win-2"}) infos := svc.ListWindowInfos() assert.Len(t, infos, 2) @@ -234,7 +236,7 @@ func TestListWindowInfos_Good(t *testing.T) { func TestSetWindowPosition_Good(t *testing.T) { c := newTestConclave(t) svc := core.MustServiceFor[*Service](c, "display") - _ = svc.OpenWindow(window.WithName("pos-win")) + requireCreateWindow(t, svc, CreateWindowOptions{Name: "pos-win"}) err := svc.SetWindowPosition("pos-win", 300, 400) assert.NoError(t, err) @@ -255,7 +257,7 @@ func TestSetWindowPosition_Bad(t *testing.T) { func TestSetWindowSize_Good(t *testing.T) { c := newTestConclave(t) svc := core.MustServiceFor[*Service](c, "display") - _ = svc.OpenWindow(window.WithName("size-win")) + requireCreateWindow(t, svc, CreateWindowOptions{Name: "size-win"}) err := svc.SetWindowSize("size-win", 1024, 768) assert.NoError(t, err) @@ -268,7 +270,7 @@ func TestSetWindowSize_Good(t *testing.T) { func TestMaximizeWindow_Good(t *testing.T) { c := newTestConclave(t) svc := core.MustServiceFor[*Service](c, "display") - _ = svc.OpenWindow(window.WithName("max-win")) + requireCreateWindow(t, svc, CreateWindowOptions{Name: "max-win"}) err := svc.MaximizeWindow("max-win") assert.NoError(t, err) @@ -280,7 +282,7 @@ func TestMaximizeWindow_Good(t *testing.T) { func TestRestoreWindow_Good(t *testing.T) { c := newTestConclave(t) svc := core.MustServiceFor[*Service](c, "display") - _ = svc.OpenWindow(window.WithName("restore-win")) + requireCreateWindow(t, svc, CreateWindowOptions{Name: "restore-win"}) _ = svc.MaximizeWindow("restore-win") err := svc.RestoreWindow("restore-win") @@ -293,7 +295,7 @@ func TestRestoreWindow_Good(t *testing.T) { func TestFocusWindow_Good(t *testing.T) { c := newTestConclave(t) svc := core.MustServiceFor[*Service](c, "display") - _ = svc.OpenWindow(window.WithName("focus-win")) + requireCreateWindow(t, svc, CreateWindowOptions{Name: "focus-win"}) err := svc.FocusWindow("focus-win") assert.NoError(t, err) @@ -305,7 +307,7 @@ func TestFocusWindow_Good(t *testing.T) { func TestCloseWindow_Good(t *testing.T) { c := newTestConclave(t) svc := core.MustServiceFor[*Service](c, "display") - _ = svc.OpenWindow(window.WithName("close-win")) + requireCreateWindow(t, svc, CreateWindowOptions{Name: "close-win"}) err := svc.CloseWindow("close-win") assert.NoError(t, err) @@ -318,7 +320,7 @@ func TestCloseWindow_Good(t *testing.T) { func TestSetWindowVisibility_Good(t *testing.T) { c := newTestConclave(t) svc := core.MustServiceFor[*Service](c, "display") - _ = svc.OpenWindow(window.WithName("vis-win")) + requireCreateWindow(t, svc, CreateWindowOptions{Name: "vis-win"}) err := svc.SetWindowVisibility("vis-win", false) assert.NoError(t, err) @@ -330,7 +332,7 @@ func TestSetWindowVisibility_Good(t *testing.T) { func TestSetWindowAlwaysOnTop_Good(t *testing.T) { c := newTestConclave(t) svc := core.MustServiceFor[*Service](c, "display") - _ = svc.OpenWindow(window.WithName("ontop-win")) + requireCreateWindow(t, svc, CreateWindowOptions{Name: "ontop-win"}) err := svc.SetWindowAlwaysOnTop("ontop-win", true) assert.NoError(t, err) @@ -339,7 +341,7 @@ func TestSetWindowAlwaysOnTop_Good(t *testing.T) { func TestSetWindowTitle_Good(t *testing.T) { c := newTestConclave(t) svc := core.MustServiceFor[*Service](c, "display") - _ = svc.OpenWindow(window.WithName("title-win")) + requireCreateWindow(t, svc, CreateWindowOptions{Name: "title-win"}) err := svc.SetWindowTitle("title-win", "New Title") assert.NoError(t, err) @@ -348,8 +350,8 @@ func TestSetWindowTitle_Good(t *testing.T) { 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")) + requireCreateWindow(t, svc, CreateWindowOptions{Name: "win-a"}) + requireCreateWindow(t, svc, CreateWindowOptions{Name: "win-b"}) _ = svc.FocusWindow("win-b") focused := svc.GetFocusedWindow() @@ -359,7 +361,7 @@ func TestGetFocusedWindow_Good(t *testing.T) { func TestGetFocusedWindow_Good_NoneSelected(t *testing.T) { c := newTestConclave(t) svc := core.MustServiceFor[*Service](c, "display") - _ = svc.OpenWindow(window.WithName("win-a")) + requireCreateWindow(t, svc, CreateWindowOptions{Name: "win-a"}) focused := svc.GetFocusedWindow() assert.Equal(t, "", focused) @@ -454,7 +456,7 @@ menu: show_dev_tools: false `), 0o644)) - s, _ := New() + s, _ := NewService() s.loadConfigFrom(cfgPath) // Verify configData was populated from file @@ -464,7 +466,7 @@ menu: } func TestLoadConfig_Bad_MissingFile(t *testing.T) { - s, _ := New() + s, _ := NewService() s.loadConfigFrom(filepath.Join(t.TempDir(), "nonexistent.yaml")) // Should not panic, configData stays at empty defaults @@ -477,7 +479,7 @@ func TestHandleConfigTask_Persists_Good(t *testing.T) { dir := t.TempDir() cfgPath := filepath.Join(dir, "config.yaml") - s, _ := New() + s, _ := NewService() s.loadConfigFrom(cfgPath) // Creates empty config (file doesn't exist yet) // Simulate a TaskSaveConfig through the handler diff --git a/pkg/mcp/mcp_test.go b/pkg/mcp/mcp_test.go index d3a3453..7b9d429 100644 --- a/pkg/mcp/mcp_test.go +++ b/pkg/mcp/mcp_test.go @@ -14,13 +14,13 @@ import ( func TestSubsystem_Good_Name(t *testing.T) { c, _ := core.New(core.WithServiceLock()) - sub := New(c) + sub := NewSubsystem(c) assert.Equal(t, "display", sub.Name()) } func TestSubsystem_Good_RegisterTools(t *testing.T) { c, _ := core.New(core.WithServiceLock()) - sub := New(c) + sub := NewSubsystem(c) // RegisterTools should not panic with a real mcp.Server server := mcp.NewServer(&mcp.Implementation{Name: "test", Version: "0.1.0"}, nil) assert.NotPanics(t, func() { sub.RegisterTools(server) }) @@ -34,7 +34,7 @@ type mockClipPlatform struct { } func (m *mockClipPlatform) Text() (string, bool) { return m.text, m.ok } -func (m *mockClipPlatform) SetText(t string) bool { m.text = t; m.ok = t != ""; return true } +func (m *mockClipPlatform) SetText(t string) bool { m.text = t; m.ok = t != ""; return true } func TestMCP_Good_ClipboardRoundTrip(t *testing.T) { c, err := core.New( diff --git a/pkg/mcp/subsystem.go b/pkg/mcp/subsystem.go index 5322dba..2d3be0c 100644 --- a/pkg/mcp/subsystem.go +++ b/pkg/mcp/subsystem.go @@ -11,12 +11,17 @@ type Subsystem struct { core *core.Core } -// New(c) creates a display MCP subsystem backed by a Core instance. -// sub := mcp.New(c); sub.RegisterTools(server) -func New(c *core.Core) *Subsystem { +// NewSubsystem creates the display MCP bridge for a Core instance. +// sub := mcp.NewSubsystem(c); sub.RegisterTools(server) +func NewSubsystem(c *core.Core) *Subsystem { return &Subsystem{core: c} } +// Deprecated: use NewSubsystem(c). +func New(c *core.Core) *Subsystem { + return NewSubsystem(c) +} + func (s *Subsystem) Name() string { return "display" } func (s *Subsystem) RegisterTools(server *mcp.Server) { diff --git a/pkg/mcp/tools_clipboard.go b/pkg/mcp/tools_clipboard.go index 2291950..82aa435 100644 --- a/pkg/mcp/tools_clipboard.go +++ b/pkg/mcp/tools_clipboard.go @@ -6,7 +6,6 @@ import ( coreerr "forge.lthn.ai/core/go-log" "forge.lthn.ai/core/gui/pkg/clipboard" - coreerr "forge.lthn.ai/core/go-log" "github.com/modelcontextprotocol/go-sdk/mcp" ) diff --git a/pkg/mcp/tools_contextmenu.go b/pkg/mcp/tools_contextmenu.go index 10d0d4a..d6da3a5 100644 --- a/pkg/mcp/tools_contextmenu.go +++ b/pkg/mcp/tools_contextmenu.go @@ -7,7 +7,6 @@ import ( coreerr "forge.lthn.ai/core/go-log" "forge.lthn.ai/core/gui/pkg/contextmenu" - coreerr "forge.lthn.ai/core/go-log" "github.com/modelcontextprotocol/go-sdk/mcp" ) diff --git a/pkg/mcp/tools_environment.go b/pkg/mcp/tools_environment.go index 8e86a18..c8fc831 100644 --- a/pkg/mcp/tools_environment.go +++ b/pkg/mcp/tools_environment.go @@ -6,7 +6,6 @@ import ( coreerr "forge.lthn.ai/core/go-log" "forge.lthn.ai/core/gui/pkg/environment" - coreerr "forge.lthn.ai/core/go-log" "github.com/modelcontextprotocol/go-sdk/mcp" ) diff --git a/pkg/menu/menu.go b/pkg/menu/menu.go index 26c552e..c916ea9 100644 --- a/pkg/menu/menu.go +++ b/pkg/menu/menu.go @@ -20,11 +20,13 @@ type Manager struct { } // NewManager creates a menu Manager. +// menu.NewManager(menu.NewWailsPlatform(app)).SetApplicationMenu([]menu.MenuItem{{Label: "File"}}) func NewManager(platform Platform) *Manager { return &Manager{platform: platform} } // Build constructs a PlatformMenu from a tree of MenuItems. +// menu.NewManager(menu.NewWailsPlatform(app)).Build([]menu.MenuItem{{Label: "File"}}) func (m *Manager) Build(items []MenuItem) PlatformMenu { menu := m.platform.NewMenu() m.buildItems(menu, items) @@ -60,6 +62,7 @@ func (m *Manager) buildItems(menu PlatformMenu, items []MenuItem) { } // SetApplicationMenu builds and sets the application menu. +// menu.NewManager(menu.NewWailsPlatform(app)).SetApplicationMenu([]menu.MenuItem{{Label: "File"}}) func (m *Manager) SetApplicationMenu(items []MenuItem) { menu := m.Build(items) m.platform.SetApplicationMenu(menu) diff --git a/pkg/systray/tray.go b/pkg/systray/tray.go index 8d2e108..817e199 100644 --- a/pkg/systray/tray.go +++ b/pkg/systray/tray.go @@ -21,6 +21,7 @@ type Manager struct { } // NewManager creates a systray Manager. +// systray.NewManager(systray.NewWailsPlatform(app)).Setup("Core", "Core") func NewManager(platform Platform) *Manager { return &Manager{ platform: platform, @@ -29,6 +30,7 @@ func NewManager(platform Platform) *Manager { } // Setup creates the system tray with default icon and tooltip. +// systray.NewManager(systray.NewWailsPlatform(app)).Setup("Core", "Core") func (m *Manager) Setup(tooltip, label string) error { m.tray = m.platform.NewTray() if m.tray == nil { diff --git a/pkg/webview/service.go b/pkg/webview/service.go index a17d0b3..2b7ccbc 100644 --- a/pkg/webview/service.go +++ b/pkg/webview/service.go @@ -43,6 +43,28 @@ type Options struct { ConsoleLimit int // Max console messages per window (default: 1000) } +func defaultOptions() Options { + return Options{ + DebugURL: "http://localhost:9222", + Timeout: 30 * time.Second, + ConsoleLimit: 1000, + } +} + +func normalizeOptions(options Options) Options { + defaults := defaultOptions() + if options.DebugURL == "" { + options.DebugURL = defaults.DebugURL + } + if options.Timeout == 0 { + options.Timeout = defaults.Timeout + } + if options.ConsoleLimit == 0 { + options.ConsoleLimit = defaults.ConsoleLimit + } + return options +} + type Service struct { *core.ServiceRuntime[Options] options Options @@ -52,18 +74,10 @@ type Service struct { watcherSetup func(conn connector, windowName string) // called after connection creation } -// Register binds the webview service to a Core instance. -// core.WithService(webview.Register()) -// core.WithService(webview.Register(func(o *Options) { o.DebugURL = "http://localhost:9223" })) -func Register(optionFns ...func(*Options)) func(*core.Core) (any, error) { - o := Options{ - DebugURL: "http://localhost:9222", - Timeout: 30 * time.Second, - ConsoleLimit: 1000, - } - for _, fn := range optionFns { - fn(&o) - } +// RegisterWithOptions binds the webview service to a Core instance using a declarative Options literal. +// core.WithService(webview.RegisterWithOptions(webview.Options{DebugURL: "http://localhost:9223", Timeout: 30 * time.Second, ConsoleLimit: 1000})) +func RegisterWithOptions(options Options) func(*core.Core) (any, error) { + o := normalizeOptions(options) return func(c *core.Core) (any, error) { svc := &Service{ ServiceRuntime: core.NewServiceRuntime[Options](c, o), @@ -76,6 +90,17 @@ func Register(optionFns ...func(*Options)) func(*core.Core) (any, error) { } } +// Deprecated: use RegisterWithOptions(webview.Options{DebugURL: "http://localhost:9223", Timeout: 30 * time.Second, ConsoleLimit: 1000}). +func Register(optionFns ...func(*Options)) func(*core.Core) (any, error) { + options := defaultOptions() + for _, fn := range optionFns { + if fn != nil { + fn(&options) + } + } + return RegisterWithOptions(options) +} + // defaultNewConn creates real go-webview connections. func defaultNewConn(options Options) func(string, string) (connector, error) { return func(debugURL, windowName string) (connector, error) { diff --git a/pkg/webview/service_test.go b/pkg/webview/service_test.go index 45a8f20..6578159 100644 --- a/pkg/webview/service_test.go +++ b/pkg/webview/service_test.go @@ -37,21 +37,41 @@ type mockConnector struct { consoleClearCalled bool } -func (m *mockConnector) Navigate(url string) error { m.lastNavURL = url; return nil } -func (m *mockConnector) Click(sel string) error { m.lastClickSel = sel; return nil } -func (m *mockConnector) Type(sel, text string) error { m.lastTypeSel = sel; m.lastTypeText = text; return nil } -func (m *mockConnector) Hover(sel string) error { m.lastHoverSel = sel; return nil } -func (m *mockConnector) Select(sel, val string) error { m.lastSelectSel = sel; m.lastSelectVal = val; return nil } -func (m *mockConnector) Check(sel string, c bool) error { m.lastCheckSel = sel; m.lastCheckVal = c; return nil } -func (m *mockConnector) Evaluate(s string) (any, error) { return m.evalResult, nil } -func (m *mockConnector) Screenshot() ([]byte, error) { return m.screenshot, nil } -func (m *mockConnector) GetURL() (string, error) { return m.url, nil } -func (m *mockConnector) GetTitle() (string, error) { return m.title, nil } +func (m *mockConnector) Navigate(url string) error { m.lastNavURL = url; return nil } +func (m *mockConnector) Click(sel string) error { m.lastClickSel = sel; return nil } +func (m *mockConnector) Type(sel, text string) error { + m.lastTypeSel = sel + m.lastTypeText = text + return nil +} +func (m *mockConnector) Hover(sel string) error { m.lastHoverSel = sel; return nil } +func (m *mockConnector) Select(sel, val string) error { + m.lastSelectSel = sel + m.lastSelectVal = val + return nil +} +func (m *mockConnector) Check(sel string, c bool) error { + m.lastCheckSel = sel + m.lastCheckVal = c + return nil +} +func (m *mockConnector) Evaluate(s string) (any, error) { return m.evalResult, nil } +func (m *mockConnector) Screenshot() ([]byte, error) { return m.screenshot, nil } +func (m *mockConnector) GetURL() (string, error) { return m.url, nil } +func (m *mockConnector) GetTitle() (string, error) { return m.title, nil } func (m *mockConnector) GetHTML(sel string) (string, error) { return m.html, nil } -func (m *mockConnector) ClearConsole() { m.consoleClearCalled = true } -func (m *mockConnector) Close() error { m.closed = true; return nil } -func (m *mockConnector) SetViewport(w, h int) error { m.lastViewportW = w; m.lastViewportH = h; return nil } -func (m *mockConnector) UploadFile(sel string, p []string) error { m.lastUploadSel = sel; m.lastUploadPaths = p; return nil } +func (m *mockConnector) ClearConsole() { m.consoleClearCalled = true } +func (m *mockConnector) Close() error { m.closed = true; return nil } +func (m *mockConnector) SetViewport(w, h int) error { + m.lastViewportW = w + m.lastViewportH = h + return nil +} +func (m *mockConnector) UploadFile(sel string, p []string) error { + m.lastUploadSel = sel + m.lastUploadPaths = p + return nil +} func (m *mockConnector) QuerySelector(sel string) (*ElementInfo, error) { if len(m.elements) > 0 { @@ -68,7 +88,7 @@ func (m *mockConnector) GetConsole() []ConsoleMessage { return m.console } func newTestService(t *testing.T, mock *mockConnector) (*Service, *core.Core) { t.Helper() - factory := Register() + factory := RegisterWithOptions(Options{}) c, err := core.New(core.WithService(factory), core.WithServiceLock()) require.NoError(t, err) require.NoError(t, c.ServiceStartup(context.Background(), nil)) @@ -78,7 +98,7 @@ func newTestService(t *testing.T, mock *mockConnector) (*Service, *core.Core) { return svc, c } -func TestRegister_Good(t *testing.T) { +func TestRegisterWithOptions_Good(t *testing.T) { svc, _ := newTestService(t, &mockConnector{}) assert.NotNil(t, svc) } diff --git a/pkg/window/messages.go b/pkg/window/messages.go index a14d464..ef63717 100644 --- a/pkg/window/messages.go +++ b/pkg/window/messages.go @@ -17,10 +17,9 @@ type QueryWindowByName struct{ Name string } type QueryConfig struct{} -type TaskOpenWindow struct { - Window *Window - Options []WindowOption -} +// TaskOpenWindow opens a concrete Window descriptor. +// window.TaskOpenWindow{Window: &window.Window{Name: "settings", URL: "/", Width: 800, Height: 600}} +type TaskOpenWindow struct{ Window *Window } type TaskCloseWindow struct{ Name string } diff --git a/pkg/window/options.go b/pkg/window/options.go index 38c5064..9e9f48f 100644 --- a/pkg/window/options.go +++ b/pkg/window/options.go @@ -1,10 +1,11 @@ // pkg/window/options.go package window -// WindowOption is a functional option applied to a Window descriptor. +// WindowOption is the compatibility layer for option-chain callers. +// Prefer a Window literal with Manager.CreateWindow. type WindowOption func(*Window) error -// ApplyOptions creates a Window and applies all options in order. +// Deprecated: use Manager.CreateWindow(Window{Name: "settings", URL: "/", Width: 800, Height: 600}). func ApplyOptions(options ...WindowOption) (*Window, error) { w := &Window{} for _, option := range options { @@ -18,6 +19,7 @@ func ApplyOptions(options ...WindowOption) (*Window, error) { return w, nil } +// Compatibility helpers for callers still using option chains. func WithName(name string) WindowOption { return func(w *Window) error { w.Name = name; return nil } } diff --git a/pkg/window/service.go b/pkg/window/service.go index fbcbbbc..731929a 100644 --- a/pkg/window/service.go +++ b/pkg/window/service.go @@ -187,15 +187,10 @@ func (s *Service) primaryScreenArea() (int, int, int, int) { } func (s *Service) taskOpenWindow(t TaskOpenWindow) (any, bool, error) { - var ( - pw PlatformWindow - err error - ) - if t.Window != nil { - pw, err = s.manager.Create(t.Window) - } else { - pw, err = s.manager.Open(t.Options...) + if t.Window == nil { + return nil, true, coreerr.E("window.taskOpenWindow", "window descriptor is required", nil) } + pw, err := s.manager.CreateWindow(*t.Window) if err != nil { return nil, true, err } diff --git a/pkg/window/service_screen_test.go b/pkg/window/service_screen_test.go index ce1e166..af86b81 100644 --- a/pkg/window/service_screen_test.go +++ b/pkg/window/service_screen_test.go @@ -49,10 +49,8 @@ func TestTaskTileWindows_Good_UsesPrimaryScreenSize(t *testing.T) { }, }) - _, _, err := c.PERFORM(TaskOpenWindow{Options: []WindowOption{WithName("left"), WithSize(400, 400)}}) - require.NoError(t, err) - _, _, err = c.PERFORM(TaskOpenWindow{Options: []WindowOption{WithName("right"), WithSize(400, 400)}}) - require.NoError(t, err) + _ = requireOpenWindow(t, c, Window{Name: "left", Width: 400, Height: 400}) + _ = requireOpenWindow(t, c, Window{Name: "right", Width: 400, Height: 400}) _, handled, err := c.PERFORM(TaskTileWindows{Mode: "left-right", Windows: []string{"left", "right"}}) require.NoError(t, err) @@ -82,8 +80,7 @@ func TestTaskSnapWindow_Good_UsesPrimaryScreenSize(t *testing.T) { }, }) - _, _, err := c.PERFORM(TaskOpenWindow{Options: []WindowOption{WithName("snap"), WithSize(400, 300)}}) - require.NoError(t, err) + _ = requireOpenWindow(t, c, Window{Name: "snap", Width: 400, Height: 300}) _, handled, err := c.PERFORM(TaskSnapWindow{Name: "snap", Position: "left"}) require.NoError(t, err) @@ -107,10 +104,8 @@ func TestTaskTileWindows_Good_UsesPrimaryWorkAreaOrigin(t *testing.T) { }, }) - _, _, err := c.PERFORM(TaskOpenWindow{Options: []WindowOption{WithName("left"), WithSize(400, 400)}}) - require.NoError(t, err) - _, _, err = c.PERFORM(TaskOpenWindow{Options: []WindowOption{WithName("right"), WithSize(400, 400)}}) - require.NoError(t, err) + _ = requireOpenWindow(t, c, Window{Name: "left", Width: 400, Height: 400}) + _ = requireOpenWindow(t, c, Window{Name: "right", Width: 400, Height: 400}) _, handled, err := c.PERFORM(TaskTileWindows{Mode: "left-right", Windows: []string{"left", "right"}}) require.NoError(t, err) diff --git a/pkg/window/service_test.go b/pkg/window/service_test.go index 6cdfc1a..3aa33f2 100644 --- a/pkg/window/service_test.go +++ b/pkg/window/service_test.go @@ -22,6 +22,14 @@ func newTestWindowService(t *testing.T) (*Service, *core.Core) { return svc, c } +func requireOpenWindow(t *testing.T, c *core.Core, window Window) WindowInfo { + t.Helper() + result, handled, err := c.PERFORM(TaskOpenWindow{Window: &window}) + require.NoError(t, err) + require.True(t, handled) + return result.(WindowInfo) +} + func TestRegister_Good(t *testing.T) { svc, _ := newTestWindowService(t) assert.NotNil(t, svc) @@ -39,15 +47,12 @@ func TestTaskOpenWindow_Good(t *testing.T) { assert.Equal(t, "test", info.Name) } -func TestTaskOpenWindow_OptionsFallback_Good(t *testing.T) { +func TestTaskOpenWindow_Bad_MissingWindow(t *testing.T) { _, c := newTestWindowService(t) - result, handled, err := c.PERFORM(TaskOpenWindow{ - Options: []WindowOption{WithName("test-fallback"), WithURL("/")}, - }) - require.NoError(t, err) + result, handled, err := c.PERFORM(TaskOpenWindow{}) assert.True(t, handled) - info := result.(WindowInfo) - assert.Equal(t, "test-fallback", info.Name) + assert.Error(t, err) + assert.Nil(t, result) } func TestTaskOpenWindow_Bad(t *testing.T) { @@ -60,8 +65,8 @@ func TestTaskOpenWindow_Bad(t *testing.T) { func TestQueryWindowList_Good(t *testing.T) { _, c := newTestWindowService(t) - _, _, _ = c.PERFORM(TaskOpenWindow{Options: []WindowOption{WithName("a")}}) - _, _, _ = c.PERFORM(TaskOpenWindow{Options: []WindowOption{WithName("b")}}) + _ = requireOpenWindow(t, c, Window{Name: "a"}) + _ = requireOpenWindow(t, c, Window{Name: "b"}) result, handled, err := c.QUERY(QueryWindowList{}) require.NoError(t, err) @@ -72,7 +77,7 @@ func TestQueryWindowList_Good(t *testing.T) { func TestQueryWindowByName_Good(t *testing.T) { _, c := newTestWindowService(t) - _, _, _ = c.PERFORM(TaskOpenWindow{Options: []WindowOption{WithName("test")}}) + _ = requireOpenWindow(t, c, Window{Name: "test"}) result, handled, err := c.QUERY(QueryWindowByName{Name: "test"}) require.NoError(t, err) @@ -91,7 +96,7 @@ func TestQueryWindowByName_Bad(t *testing.T) { func TestTaskCloseWindow_Good(t *testing.T) { _, c := newTestWindowService(t) - _, _, _ = c.PERFORM(TaskOpenWindow{Options: []WindowOption{WithName("test")}}) + _ = requireOpenWindow(t, c, Window{Name: "test"}) _, handled, err := c.PERFORM(TaskCloseWindow{Name: "test"}) require.NoError(t, err) @@ -111,7 +116,7 @@ func TestTaskCloseWindow_Bad(t *testing.T) { func TestTaskSetPosition_Good(t *testing.T) { _, c := newTestWindowService(t) - _, _, _ = c.PERFORM(TaskOpenWindow{Options: []WindowOption{WithName("test")}}) + _ = requireOpenWindow(t, c, Window{Name: "test"}) _, handled, err := c.PERFORM(TaskSetPosition{Name: "test", X: 100, Y: 200}) require.NoError(t, err) @@ -125,7 +130,7 @@ func TestTaskSetPosition_Good(t *testing.T) { func TestTaskSetSize_Good(t *testing.T) { _, c := newTestWindowService(t) - _, _, _ = c.PERFORM(TaskOpenWindow{Options: []WindowOption{WithName("test")}}) + _ = requireOpenWindow(t, c, Window{Name: "test"}) _, handled, err := c.PERFORM(TaskSetSize{Name: "test", Width: 800, Height: 600}) require.NoError(t, err) @@ -139,7 +144,7 @@ func TestTaskSetSize_Good(t *testing.T) { func TestTaskMaximise_Good(t *testing.T) { _, c := newTestWindowService(t) - _, _, _ = c.PERFORM(TaskOpenWindow{Options: []WindowOption{WithName("test")}}) + _ = requireOpenWindow(t, c, Window{Name: "test"}) _, handled, err := c.PERFORM(TaskMaximise{Name: "test"}) require.NoError(t, err) @@ -154,10 +159,7 @@ func TestFileDrop_Good(t *testing.T) { _, c := newTestWindowService(t) // Open a window - result, _, _ := c.PERFORM(TaskOpenWindow{ - Options: []WindowOption{WithName("drop-test")}, - }) - info := result.(WindowInfo) + info := requireOpenWindow(t, c, Window{Name: "drop-test"}) assert.Equal(t, "drop-test", info.Name) // Capture broadcast actions @@ -190,7 +192,7 @@ func TestFileDrop_Good(t *testing.T) { func TestTaskMinimise_Good(t *testing.T) { svc, c := newTestWindowService(t) - _, _, _ = c.PERFORM(TaskOpenWindow{Options: []WindowOption{WithName("test")}}) + _ = requireOpenWindow(t, c, Window{Name: "test"}) _, handled, err := c.PERFORM(TaskMinimise{Name: "test"}) require.NoError(t, err) @@ -213,7 +215,7 @@ func TestTaskMinimise_Bad(t *testing.T) { func TestTaskFocus_Good(t *testing.T) { svc, c := newTestWindowService(t) - _, _, _ = c.PERFORM(TaskOpenWindow{Options: []WindowOption{WithName("test")}}) + _ = requireOpenWindow(t, c, Window{Name: "test"}) _, handled, err := c.PERFORM(TaskFocus{Name: "test"}) require.NoError(t, err) @@ -236,7 +238,7 @@ func TestTaskFocus_Bad(t *testing.T) { func TestTaskRestore_Good(t *testing.T) { svc, c := newTestWindowService(t) - _, _, _ = c.PERFORM(TaskOpenWindow{Options: []WindowOption{WithName("test")}}) + _ = requireOpenWindow(t, c, Window{Name: "test"}) // First maximise, then restore _, _, _ = c.PERFORM(TaskMaximise{Name: "test"}) @@ -267,7 +269,7 @@ func TestTaskRestore_Bad(t *testing.T) { func TestTaskSetTitle_Good(t *testing.T) { svc, c := newTestWindowService(t) - _, _, _ = c.PERFORM(TaskOpenWindow{Options: []WindowOption{WithName("test")}}) + _ = requireOpenWindow(t, c, Window{Name: "test"}) _, handled, err := c.PERFORM(TaskSetTitle{Name: "test", Title: "New Title"}) require.NoError(t, err) @@ -289,7 +291,7 @@ func TestTaskSetTitle_Bad(t *testing.T) { func TestTaskSetAlwaysOnTop_Good(t *testing.T) { svc, c := newTestWindowService(t) - _, _, _ = c.PERFORM(TaskOpenWindow{Options: []WindowOption{WithName("test")}}) + _ = requireOpenWindow(t, c, Window{Name: "test"}) _, handled, err := c.PERFORM(TaskSetAlwaysOnTop{Name: "test", AlwaysOnTop: true}) require.NoError(t, err) @@ -312,7 +314,7 @@ func TestTaskSetAlwaysOnTop_Bad(t *testing.T) { func TestTaskSetBackgroundColour_Good(t *testing.T) { svc, c := newTestWindowService(t) - _, _, _ = c.PERFORM(TaskOpenWindow{Options: []WindowOption{WithName("test")}}) + _ = requireOpenWindow(t, c, Window{Name: "test"}) _, handled, err := c.PERFORM(TaskSetBackgroundColour{ Name: "test", Red: 10, Green: 20, Blue: 30, Alpha: 40, @@ -337,7 +339,7 @@ func TestTaskSetBackgroundColour_Bad(t *testing.T) { func TestTaskSetVisibility_Good(t *testing.T) { svc, c := newTestWindowService(t) - _, _, _ = c.PERFORM(TaskOpenWindow{Options: []WindowOption{WithName("test")}}) + _ = requireOpenWindow(t, c, Window{Name: "test"}) _, handled, err := c.PERFORM(TaskSetVisibility{Name: "test", Visible: true}) require.NoError(t, err) @@ -366,7 +368,7 @@ func TestTaskSetVisibility_Bad(t *testing.T) { func TestTaskFullscreen_Good(t *testing.T) { svc, c := newTestWindowService(t) - _, _, _ = c.PERFORM(TaskOpenWindow{Options: []WindowOption{WithName("test")}}) + _ = requireOpenWindow(t, c, Window{Name: "test"}) // Enter fullscreen _, handled, err := c.PERFORM(TaskFullscreen{Name: "test", Fullscreen: true}) @@ -396,8 +398,8 @@ func TestTaskFullscreen_Bad(t *testing.T) { func TestTaskSaveLayout_Good(t *testing.T) { svc, c := newTestWindowService(t) - _, _, _ = c.PERFORM(TaskOpenWindow{Options: []WindowOption{WithName("editor"), WithSize(960, 1080), WithPosition(0, 0)}}) - _, _, _ = c.PERFORM(TaskOpenWindow{Options: []WindowOption{WithName("terminal"), WithSize(960, 1080), WithPosition(960, 0)}}) + _ = requireOpenWindow(t, c, Window{Name: "editor", Width: 960, Height: 1080, X: 0, Y: 0}) + _ = requireOpenWindow(t, c, Window{Name: "terminal", Width: 960, Height: 1080, X: 960, Y: 0}) _, handled, err := c.PERFORM(TaskSaveLayout{Name: "coding"}) require.NoError(t, err) @@ -433,8 +435,8 @@ func TestTaskSaveLayout_Bad(t *testing.T) { func TestTaskRestoreLayout_Good(t *testing.T) { svc, c := newTestWindowService(t) // Open windows - _, _, _ = c.PERFORM(TaskOpenWindow{Options: []WindowOption{WithName("editor"), WithSize(800, 600), WithPosition(0, 0)}}) - _, _, _ = c.PERFORM(TaskOpenWindow{Options: []WindowOption{WithName("terminal"), WithSize(800, 600), WithPosition(0, 0)}}) + _ = requireOpenWindow(t, c, Window{Name: "editor", Width: 800, Height: 600, X: 0, Y: 0}) + _ = requireOpenWindow(t, c, Window{Name: "terminal", Width: 800, Height: 600, X: 0, Y: 0}) // Save a layout with specific positions _, _, _ = c.PERFORM(TaskSaveLayout{Name: "coding"}) @@ -483,8 +485,8 @@ func TestTaskRestoreLayout_Bad(t *testing.T) { func TestTaskStackWindows_Good(t *testing.T) { svc, c := newTestWindowService(t) - _, _, _ = c.PERFORM(TaskOpenWindow{Options: []WindowOption{WithName("s1"), WithSize(800, 600)}}) - _, _, _ = c.PERFORM(TaskOpenWindow{Options: []WindowOption{WithName("s2"), WithSize(800, 600)}}) + _ = requireOpenWindow(t, c, Window{Name: "s1", Width: 800, Height: 600}) + _ = requireOpenWindow(t, c, Window{Name: "s2", Width: 800, Height: 600}) _, handled, err := c.PERFORM(TaskStackWindows{Windows: []string{"s1", "s2"}, OffsetX: 25, OffsetY: 35}) require.NoError(t, err) @@ -501,8 +503,8 @@ func TestTaskStackWindows_Good(t *testing.T) { func TestTaskApplyWorkflow_Good(t *testing.T) { svc, c := newTestWindowService(t) - _, _, _ = c.PERFORM(TaskOpenWindow{Options: []WindowOption{WithName("editor"), WithSize(800, 600)}}) - _, _, _ = c.PERFORM(TaskOpenWindow{Options: []WindowOption{WithName("terminal"), WithSize(800, 600)}}) + _ = requireOpenWindow(t, c, Window{Name: "editor", Width: 800, Height: 600}) + _ = requireOpenWindow(t, c, Window{Name: "terminal", Width: 800, Height: 600}) _, handled, err := c.PERFORM(TaskApplyWorkflow{Workflow: "side-by-side"}) require.NoError(t, err) diff --git a/pkg/window/window.go b/pkg/window/window.go index d6aaaf0..6334b42 100644 --- a/pkg/window/window.go +++ b/pkg/window/window.go @@ -49,6 +49,7 @@ type Manager struct { } // NewManager creates a window Manager with the given platform backend. +// window.NewManager(window.NewWailsPlatform(app)) func NewManager(platform Platform) *Manager { return &Manager{ platform: platform, @@ -59,7 +60,7 @@ func NewManager(platform Platform) *Manager { } // NewManagerWithDir creates a window Manager with a custom config directory for state/layout persistence. -// Useful for testing or when the default config directory is not appropriate. +// window.NewManagerWithDir(window.NewMockPlatform(), t.TempDir()) func NewManagerWithDir(platform Platform, configDir string) *Manager { return &Manager{ platform: platform, @@ -81,51 +82,70 @@ func (m *Manager) SetDefaultHeight(height int) { } } -// Open creates a window using functional options, applies saved state, and tracks it. +// Deprecated: use CreateWindow(Window{Name: "settings", URL: "/settings", Width: 800, Height: 600}). func (m *Manager) Open(options ...WindowOption) (PlatformWindow, error) { w, err := ApplyOptions(options...) if err != nil { return nil, coreerr.E("window.Manager.Open", "failed to apply options", err) } - return m.Create(w) + return m.CreateWindow(*w) } -// Create creates a window from a Window descriptor. +// CreateWindow creates a window from a Window descriptor. +// window.NewManager(window.NewWailsPlatform(app)).CreateWindow(window.Window{Name: "settings", URL: "/settings", Width: 800, Height: 600}) +func (m *Manager) CreateWindow(spec Window) (PlatformWindow, error) { + _, pw, err := m.createWindow(spec) + return pw, err +} + +// Deprecated: use CreateWindow(Window{Name: "settings", URL: "/settings", Width: 800, Height: 600}). func (m *Manager) Create(w *Window) (PlatformWindow, error) { - if w.Name == "" { - w.Name = "main" + if w == nil { + return nil, coreerr.E("window.Manager.Create", "window descriptor is required", nil) } - if w.Title == "" { - w.Title = "Core" + spec, pw, err := m.createWindow(*w) + if err != nil { + return nil, err } - if w.Width == 0 { + *w = spec + return pw, nil +} + +func (m *Manager) createWindow(spec Window) (Window, PlatformWindow, error) { + if spec.Name == "" { + spec.Name = "main" + } + if spec.Title == "" { + spec.Title = "Core" + } + if spec.Width == 0 { if m.defaultWidth > 0 { - w.Width = m.defaultWidth + spec.Width = m.defaultWidth } else { - w.Width = 1280 + spec.Width = 1280 } } - if w.Height == 0 { + if spec.Height == 0 { if m.defaultHeight > 0 { - w.Height = m.defaultHeight + spec.Height = m.defaultHeight } else { - w.Height = 800 + spec.Height = 800 } } - if w.URL == "" { - w.URL = "/" + if spec.URL == "" { + spec.URL = "/" } - // Apply saved state if available - m.state.ApplyState(w) + // Apply saved state if available. + m.state.ApplyState(&spec) - pw := m.platform.CreateWindow(w.ToPlatformOptions()) + pw := m.platform.CreateWindow(spec.ToPlatformOptions()) m.mu.Lock() - m.windows[w.Name] = pw + m.windows[spec.Name] = pw m.mu.Unlock() - return pw, nil + return spec, pw, nil } // Get returns a tracked window by name. diff --git a/pkg/window/window_test.go b/pkg/window/window_test.go index e7d0de4..94b4ef1 100644 --- a/pkg/window/window_test.go +++ b/pkg/window/window_test.go @@ -91,39 +91,48 @@ func newTestManager() (*Manager, *mockPlatform) { return m, p } -func TestManager_Open_Good(t *testing.T) { - m, p := newTestManager() - pw, err := m.Open(WithName("test"), WithTitle("Test"), WithURL("/test"), WithSize(800, 600)) +func requireCreateWindow(t *testing.T, m *Manager, w Window) PlatformWindow { + t.Helper() + pw, err := m.CreateWindow(w) require.NoError(t, err) + return pw +} + +func TestManager_CreateWindow_Good(t *testing.T) { + m, p := newTestManager() + pw := requireCreateWindow(t, m, Window{ + Name: "test", + Title: "Test", + URL: "/test", + Width: 800, + Height: 600, + }) assert.NotNil(t, pw) assert.Equal(t, "test", pw.Name()) assert.Len(t, p.windows, 1) } -func TestManager_Open_Defaults_Good(t *testing.T) { +func TestManager_CreateWindow_Defaults_Good(t *testing.T) { m, _ := newTestManager() - pw, err := m.Open() - require.NoError(t, err) + pw := requireCreateWindow(t, m, Window{}) assert.Equal(t, "main", pw.Name()) w, h := pw.Size() assert.Equal(t, 1280, w) assert.Equal(t, 800, h) } -func TestManager_Open_CustomDefaults_Good(t *testing.T) { +func TestManager_CreateWindow_CustomDefaults_Good(t *testing.T) { m, _ := newTestManager() m.SetDefaultWidth(1440) m.SetDefaultHeight(900) - pw, err := m.Open() - require.NoError(t, err) - + pw := requireCreateWindow(t, m, Window{}) w, h := pw.Size() assert.Equal(t, 1440, w) assert.Equal(t, 900, h) } -func TestManager_Open_Bad(t *testing.T) { +func TestManager_Open_Compatibility_Bad(t *testing.T) { m, _ := newTestManager() _, err := m.Open(func(w *Window) error { return assert.AnError }) assert.Error(t, err) @@ -131,7 +140,7 @@ func TestManager_Open_Bad(t *testing.T) { func TestManager_Get_Good(t *testing.T) { m, _ := newTestManager() - _, _ = m.Open(WithName("findme")) + _ = requireCreateWindow(t, m, Window{Name: "findme"}) pw, ok := m.Get("findme") assert.True(t, ok) assert.Equal(t, "findme", pw.Name()) @@ -145,8 +154,8 @@ func TestManager_Get_Bad(t *testing.T) { func TestManager_List_Good(t *testing.T) { m, _ := newTestManager() - _, _ = m.Open(WithName("a")) - _, _ = m.Open(WithName("b")) + _ = requireCreateWindow(t, m, Window{Name: "a"}) + _ = requireCreateWindow(t, m, Window{Name: "b"}) names := m.List() assert.Len(t, names, 2) assert.Contains(t, names, "a") @@ -155,7 +164,7 @@ func TestManager_List_Good(t *testing.T) { func TestManager_Remove_Good(t *testing.T) { m, _ := newTestManager() - _, _ = m.Open(WithName("temp")) + _ = requireCreateWindow(t, m, Window{Name: "temp"}) m.Remove("temp") _, ok := m.Get("temp") assert.False(t, ok) @@ -170,8 +179,8 @@ func TestTileMode_String_Good(t *testing.T) { func TestManager_TileWindows_Good(t *testing.T) { m, _ := newTestManager() - _, _ = m.Open(WithName("a"), WithSize(800, 600)) - _, _ = m.Open(WithName("b"), WithSize(800, 600)) + _ = requireCreateWindow(t, m, Window{Name: "a", Width: 800, Height: 600}) + _ = requireCreateWindow(t, m, Window{Name: "b", Width: 800, Height: 600}) err := m.TileWindows(TileModeLeftRight, []string{"a", "b"}, 1920, 1080) require.NoError(t, err) a, _ := m.Get("a") @@ -190,7 +199,7 @@ func TestManager_TileWindows_Bad(t *testing.T) { func TestManager_SnapWindow_Good(t *testing.T) { m, _ := newTestManager() - _, _ = m.Open(WithName("snap"), WithSize(800, 600)) + _ = requireCreateWindow(t, m, Window{Name: "snap", Width: 800, Height: 600}) err := m.SnapWindow("snap", SnapLeft, 1920, 1080) require.NoError(t, err) w, _ := m.Get("snap") @@ -202,8 +211,8 @@ func TestManager_SnapWindow_Good(t *testing.T) { func TestManager_StackWindows_Good(t *testing.T) { m, _ := newTestManager() - _, _ = m.Open(WithName("s1"), WithSize(800, 600)) - _, _ = m.Open(WithName("s2"), WithSize(800, 600)) + _ = requireCreateWindow(t, m, Window{Name: "s1", Width: 800, Height: 600}) + _ = requireCreateWindow(t, m, Window{Name: "s2", Width: 800, Height: 600}) err := m.StackWindows([]string{"s1", "s2"}, 30, 30) require.NoError(t, err) s2, _ := m.Get("s2") @@ -244,10 +253,9 @@ func TestTileWindows_AllModes_Good(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { m, _ := newTestManager() - _, err := m.Open(WithName("win"), WithSize(800, 600)) - require.NoError(t, err) + _ = requireCreateWindow(t, m, Window{Name: "win", Width: 800, Height: 600}) - err = m.TileWindows(tc.mode, []string{"win"}, screenW, screenH) + err := m.TileWindows(tc.mode, []string{"win"}, screenW, screenH) require.NoError(t, err) pw, ok := m.Get("win") @@ -290,10 +298,9 @@ func TestSnapWindow_AllPositions_Good(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { m, _ := newTestManager() - _, err := m.Open(WithName("snap"), WithSize(tc.initW, tc.initH)) - require.NoError(t, err) + _ = requireCreateWindow(t, m, Window{Name: "snap", Width: tc.initW, Height: tc.initH}) - err = m.SnapWindow("snap", tc.pos, screenW, screenH) + err := m.SnapWindow("snap", tc.pos, screenW, screenH) require.NoError(t, err) pw, ok := m.Get("snap") @@ -313,8 +320,7 @@ func TestStackWindows_ThreeWindows_Good(t *testing.T) { m, _ := newTestManager() names := []string{"s1", "s2", "s3"} for _, name := range names { - _, err := m.Open(WithName(name), WithSize(800, 600)) - require.NoError(t, err) + _ = requireCreateWindow(t, m, Window{Name: name, Width: 800, Height: 600}) } err := m.StackWindows(names, 30, 30) @@ -369,12 +375,10 @@ func TestApplyWorkflow_AllLayouts_Good(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { m, _ := newTestManager() - _, err := m.Open(WithName("editor"), WithSize(800, 600)) - require.NoError(t, err) - _, err = m.Open(WithName("terminal"), WithSize(800, 600)) - require.NoError(t, err) + _ = requireCreateWindow(t, m, Window{Name: "editor", Width: 800, Height: 600}) + _ = requireCreateWindow(t, m, Window{Name: "terminal", Width: 800, Height: 600}) - err = m.ApplyWorkflow(tc.workflow, []string{"editor", "terminal"}, screenW, screenH) + err := m.ApplyWorkflow(tc.workflow, []string{"editor", "terminal"}, screenW, screenH) require.NoError(t, err) pw0, ok := m.Get("editor")