Merge pull request '[agent/codex:gpt-5.4-mini] Update the code against the AX design principles in ~/spec/r...' (#6) from agent/update-the-code-against-the-ax-design-pr into dev
This commit is contained in:
commit
4533018f80
18 changed files with 267 additions and 191 deletions
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue