refactor(ax): simplify display and webview constructors
Some checks failed
Security Scan / security (push) Failing after 28s
Test / test (push) Successful in 1m23s

Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
Virgil 2026-03-31 08:52:54 +00:00
parent 762806d316
commit 3098fc471a
6 changed files with 87 additions and 48 deletions

View file

@ -5,7 +5,7 @@ Complete API reference for the Display service (`pkg/display`).
## Service Creation
```go
func NewService() (*Service, error)
func NewService() *Service
```
## Window Management

View file

@ -42,25 +42,23 @@ type Service struct {
events *WebSocketEventManager
}
// NewService constructs the display service.
func NewService() (*Service, error) {
// Display services start with an empty config cache.
// svc := display.NewService()
func NewService() *Service {
return &Service{
configData: map[string]map[string]any{
"window": {},
"systray": {},
"menu": {},
},
}, nil
}
}
// Register creates a factory closure that captures the Wails app.
// Pass nil for testing without a Wails runtime.
// Build a Core factory without an option chain.
// factory := display.Register(nil)
func Register(wailsApp *application.App) func(*core.Core) (any, error) {
return func(c *core.Core) (any, error) {
s, err := NewService()
if err != nil {
return nil, err
}
s := NewService()
s.ServiceRuntime = core.NewServiceRuntime[Options](c, Options{})
s.wailsApp = wailsApp
return s, nil

View file

@ -48,16 +48,13 @@ func newTestConclave(t *testing.T) *core.Core {
func TestNewService(t *testing.T) {
t.Run("creates service successfully", func(t *testing.T) {
service, err := NewService()
assert.NoError(t, err)
service := NewService()
assert.NotNil(t, service, "NewService() should return a non-nil service instance")
})
t.Run("returns independent instances", func(t *testing.T) {
service1, err1 := NewService()
service2, err2 := NewService()
assert.NoError(t, err1)
assert.NoError(t, err2)
service1 := NewService()
service2 := NewService()
assert.NotSame(t, service1, service2, "NewService() should return different instances")
})
}
@ -500,7 +497,7 @@ menu:
show_dev_tools: false
`), 0o644))
s, _ := NewService()
s := NewService()
s.loadConfigFrom(cfgPath)
// Verify configData was populated from file
@ -510,7 +507,7 @@ menu:
}
func TestLoadConfig_Bad_MissingFile(t *testing.T) {
s, _ := NewService()
s := NewService()
s.loadConfigFrom(filepath.Join(t.TempDir(), "nonexistent.yaml"))
// Should not panic, configData stays at empty defaults
@ -523,7 +520,7 @@ func TestHandleConfigTask_Persists_Good(t *testing.T) {
dir := t.TempDir()
cfgPath := filepath.Join(dir, "config.yaml")
s, _ := NewService()
s := NewService()
s.loadConfigFrom(cfgPath) // Creates empty config (file doesn't exist yet)
// Simulate a TaskSaveConfig through the handler

View file

@ -8,7 +8,7 @@ The backend is written in Go and uses the `forge.lthn.ai/core/gui/pkg/display` p
The `Service` struct is the main entry point for the display logic.
- **Initialization:**
- `NewService() (*Service, error)`: Creates a new instance of the service.
- `NewService() *Service`: Creates a new instance of the service.
- `Register(wailsApp *application.App) func(*core.Core) (any, error)`: Captures the Wails app and registers the service with Core.
- `OnStartup(ctx context.Context) error`: Loads config and registers IPC handlers.

View file

@ -54,28 +54,35 @@ type Service struct {
watcherSetup func(conn connector, windowName string) // called after connection creation
}
// Register creates a factory closure with the given options.
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)
}
// Build a Core factory from one declarative Options value.
// factory := webview.Register(webview.Options{DebugURL: "http://localhost:9333"})
func Register(options Options) func(*core.Core) (any, error) {
options = defaultOptions(options)
return func(c *core.Core) (any, error) {
svc := &Service{
ServiceRuntime: core.NewServiceRuntime[Options](c, o),
options: o,
ServiceRuntime: core.NewServiceRuntime[Options](c, options),
options: options,
connections: make(map[string]connector),
newConn: defaultNewConn(o),
newConn: defaultNewConn(options),
}
svc.watcherSetup = svc.defaultWatcherSetup
return svc, nil
}
}
func defaultOptions(options Options) Options {
if options.DebugURL == "" {
options.DebugURL = "http://localhost:9222"
}
if options.Timeout <= 0 {
options.Timeout = 30 * time.Second
}
if options.ConsoleLimit <= 0 {
options.ConsoleLimit = 1000
}
return options
}
// defaultNewConn creates real go-webview connections.
func defaultNewConn(options Options) func(string, string) (connector, error) {
return func(debugURL, windowName string) (connector, error) {

View file

@ -4,6 +4,7 @@ package webview
import (
"context"
"testing"
"time"
"forge.lthn.ai/core/go/pkg/core"
"forge.lthn.ai/core/gui/pkg/window"
@ -37,21 +38,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 +89,7 @@ func (m *mockConnector) GetConsole() []ConsoleMessage { return m.console }
func newTestService(t *testing.T, mock *mockConnector) (*Service, *core.Core) {
t.Helper()
factory := Register()
factory := Register(Options{})
c, err := core.New(core.WithService(factory), core.WithServiceLock())
require.NoError(t, err)
require.NoError(t, c.ServiceStartup(context.Background(), nil))
@ -83,6 +104,22 @@ func TestRegister_Good(t *testing.T) {
assert.NotNil(t, svc)
}
func TestRegister_Good_UsesOptions(t *testing.T) {
options := Options{
DebugURL: "http://localhost:9333",
Timeout: 45 * time.Second,
ConsoleLimit: 12,
}
factory := Register(options)
c, err := core.New(core.WithService(factory), core.WithServiceLock())
require.NoError(t, err)
require.NoError(t, c.ServiceStartup(context.Background(), nil))
svc := core.MustServiceFor[*Service](c, "webview")
assert.Equal(t, options, svc.options)
}
func TestQueryURL_Good(t *testing.T) {
_, c := newTestService(t, &mockConnector{url: "https://example.com"})
result, handled, err := c.QUERY(QueryURL{Window: "main"})