gui/pkg/display/api_test.go
Snider 4b8a534920
Some checks are pending
Security Scan / security (push) Waiting to run
Test / test (push) Waiting to run
Harden screen query failure handling
2026-04-17 18:44:19 +01:00

619 lines
16 KiB
Go

package display
import (
"bytes"
"context"
"testing"
core "dappco.re/go/core"
"forge.lthn.ai/core/gui/pkg/clipboard"
"forge.lthn.ai/core/gui/pkg/dialog"
"forge.lthn.ai/core/gui/pkg/environment"
"forge.lthn.ai/core/gui/pkg/screen"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func newTestDisplayAPIService(t *testing.T) (*Service, *core.Core) {
t.Helper()
return newTestDisplayService(t)
}
func TestDisplayAPI_screenToDisplay_Good(t *testing.T) {
got := screenToDisplay(&screen.Screen{
ID: "screen-1",
Name: "Primary",
ScaleFactor: 2,
Bounds: screen.Rect{X: 10, Y: 20, Width: 1920, Height: 1080},
IsPrimary: true,
})
require.NotNil(t, got)
assert.Equal(t, "screen-1", got.ID)
assert.Equal(t, "Primary", got.Name)
assert.Equal(t, 10, got.X)
assert.Equal(t, 20, got.Y)
assert.Equal(t, 1920, got.Width)
assert.Equal(t, 1080, got.Height)
assert.Equal(t, 2.0, got.ScaleFactor)
assert.True(t, got.IsPrimary)
}
func TestDisplayAPI_screenToDisplay_Bad(t *testing.T) {
assert.Nil(t, screenToDisplay(nil))
}
func TestDisplayAPI_screenToDisplay_Ugly(t *testing.T) {
got := screenToDisplay(&screen.Screen{})
require.NotNil(t, got)
assert.Zero(t, got.ID)
assert.Zero(t, got.Name)
assert.Zero(t, got.Width)
assert.Zero(t, got.Height)
}
func TestDisplayAPI_toDialogOpenFileOptions_Good(t *testing.T) {
got := toDialogOpenFileOptions(OpenFileOptions{
Title: "Pick",
DefaultDirectory: "/tmp",
DefaultFilename: "report.csv",
AllowMultiple: true,
Filters: []FileFilter{
{DisplayName: "CSV", Pattern: "*.csv"},
},
})
assert.Equal(t, "Pick", got.Title)
assert.Equal(t, "/tmp", got.Directory)
assert.Equal(t, "report.csv", got.Filename)
assert.True(t, got.AllowMultiple)
require.Len(t, got.Filters, 1)
assert.Equal(t, "CSV", got.Filters[0].DisplayName)
assert.Equal(t, "*.csv", got.Filters[0].Pattern)
}
func TestDisplayAPI_toDialogOpenFileOptions_Bad(t *testing.T) {
got := toDialogOpenFileOptions(OpenFileOptions{})
assert.Empty(t, got.Title)
assert.Empty(t, got.Directory)
assert.Empty(t, got.Filename)
assert.False(t, got.AllowMultiple)
assert.Nil(t, got.Filters)
}
func TestDisplayAPI_toDialogOpenFileOptions_Ugly(t *testing.T) {
got := toDialogOpenFileOptions(OpenFileOptions{
Filters: []FileFilter{
{DisplayName: "All", Pattern: "*.*"},
{DisplayName: "Media", Pattern: "*.png;*.jpg"},
},
})
require.Len(t, got.Filters, 2)
assert.Equal(t, "All", got.Filters[0].DisplayName)
assert.Equal(t, "*.png;*.jpg", got.Filters[1].Pattern)
}
func TestDisplayAPI_trayMenuItemsToSystray_Good(t *testing.T) {
got := trayMenuItemsToSystray([]TrayMenuItem{
{Label: "Open", ActionID: "open"},
{IsSeparator: true},
{
Label: "More",
ActionID: "more",
Children: []TrayMenuItem{{Label: "Nested", ActionID: "nested"}},
},
})
require.Len(t, got, 3)
assert.Equal(t, "Open", got[0].Label)
assert.Equal(t, "separator", got[1].Type)
require.Len(t, got[2].Submenu, 1)
assert.Equal(t, "nested", got[2].Submenu[0].ActionID)
}
func TestDisplayAPI_trayMenuItemsToSystray_Bad(t *testing.T) {
assert.Nil(t, trayMenuItemsToSystray(nil))
}
func TestDisplayAPI_trayMenuItemsToSystray_Ugly(t *testing.T) {
got := trayMenuItemsToSystray([]TrayMenuItem{{Children: []TrayMenuItem{{IsSeparator: true}}}})
require.Len(t, got, 1)
require.Len(t, got[0].Submenu, 1)
assert.Equal(t, "separator", got[0].Submenu[0].Type)
}
func TestDisplayAPI_GetScreens_Good(t *testing.T) {
svc, c := newTestDisplayAPIService(t)
c.RegisterQuery(func(_ *core.Core, q core.Query) core.Result {
switch q.(type) {
case screen.QueryAll:
return core.Result{Value: []screen.Screen{
{
ID: "screen-1",
Name: "Primary",
Bounds: screen.Rect{X: 10, Y: 20, Width: 1920, Height: 1080},
ScaleFactor: 2,
IsPrimary: true,
},
}, OK: true}
default:
return core.Result{}
}
})
screens := svc.GetScreens()
require.Len(t, screens, 1)
assert.Equal(t, "screen-1", screens[0].ID)
assert.Equal(t, 10, screens[0].X)
assert.Equal(t, 1920, screens[0].Width)
}
func TestDisplayAPI_GetScreens_Empty(t *testing.T) {
svc, c := newTestDisplayAPIService(t)
c.RegisterQuery(func(_ *core.Core, q core.Query) core.Result {
switch q.(type) {
case screen.QueryAll:
return core.Result{Value: []screen.Screen{}, OK: true}
default:
return core.Result{}
}
})
assert.Nil(t, svc.GetScreens())
}
func TestDisplayAPI_GetScreens_Bad(t *testing.T) {
svc, c := newTestDisplayAPIService(t)
c.RegisterQuery(func(_ *core.Core, q core.Query) core.Result {
switch q.(type) {
case screen.QueryAll:
return core.Result{Value: []string{"unexpected"}, OK: true}
default:
return core.Result{}
}
})
screens := svc.GetScreens()
require.NotNil(t, screens)
assert.Empty(t, screens)
}
func TestDisplayAPI_GetScreens_Ugly(t *testing.T) {
svc, c := newTestDisplayAPIService(t)
c.RegisterQuery(func(_ *core.Core, q core.Query) core.Result {
switch q.(type) {
case screen.QueryAll:
return core.Result{OK: false}
default:
return core.Result{}
}
})
screens := svc.GetScreens()
require.NotNil(t, screens)
assert.Empty(t, screens)
}
func TestDisplayAPI_GetScreen_BadType(t *testing.T) {
svc, c := newTestDisplayAPIService(t)
c.RegisterQuery(func(_ *core.Core, q core.Query) core.Result {
switch q.(type) {
case screen.QueryByID:
return core.Result{Value: "unexpected", OK: true}
default:
return core.Result{}
}
})
got, err := svc.GetScreen("screen-1")
require.Error(t, err)
assert.Nil(t, got)
}
func TestDisplayAPI_GetScreen_Ugly(t *testing.T) {
svc, c := newTestDisplayAPIService(t)
c.RegisterQuery(func(_ *core.Core, q core.Query) core.Result {
switch q.(type) {
case screen.QueryByID:
return core.Result{OK: false}
default:
return core.Result{}
}
})
got, err := svc.GetScreen("screen-1")
require.Error(t, err)
assert.Nil(t, got)
}
func TestDisplayAPI_GetPrimaryScreen_Ugly(t *testing.T) {
svc, c := newTestDisplayAPIService(t)
c.RegisterQuery(func(_ *core.Core, q core.Query) core.Result {
switch q.(type) {
case screen.QueryPrimary:
return core.Result{OK: false}
default:
return core.Result{}
}
})
got, err := svc.GetPrimaryScreen()
require.Error(t, err)
assert.Nil(t, got)
}
func TestDisplayAPI_GetScreenAtPoint_Ugly(t *testing.T) {
svc, c := newTestDisplayAPIService(t)
c.RegisterQuery(func(_ *core.Core, q core.Query) core.Result {
switch q.(type) {
case screen.QueryAtPoint:
return core.Result{OK: false}
default:
return core.Result{}
}
})
got, err := svc.GetScreenAtPoint(10, 20)
require.Error(t, err)
assert.Nil(t, got)
}
func TestDisplayAPI_OpenFileDialog_Good(t *testing.T) {
svc, c := newTestDisplayAPIService(t)
c.Action("dialog.openFile", func(_ context.Context, opts core.Options) core.Result {
task := opts.Get("task").Value.(dialog.TaskOpenFile)
assert.Equal(t, "Pick file", task.Options.Title)
assert.True(t, task.Options.AllowMultiple)
return core.Result{Value: []string{"/tmp/a.txt", "/tmp/b.txt"}, OK: true}
})
paths, err := svc.OpenFileDialog(OpenFileOptions{
Title: "Pick file",
AllowMultiple: true,
})
require.NoError(t, err)
assert.Equal(t, []string{"/tmp/a.txt", "/tmp/b.txt"}, paths)
}
func TestDisplayAPI_OpenFileDialog_BadType(t *testing.T) {
svc, c := newTestDisplayAPIService(t)
c.Action("dialog.openFile", func(_ context.Context, _ core.Options) core.Result {
return core.Result{Value: 42, OK: true}
})
paths, err := svc.OpenFileDialog(OpenFileOptions{})
require.Error(t, err)
assert.Nil(t, paths)
}
func TestDisplayAPI_OpenFileDialog_Bad(t *testing.T) {
svc, c := newTestDisplayAPIService(t)
c.Action("dialog.openFile", func(_ context.Context, _ core.Options) core.Result {
return core.Result{Value: assert.AnError, OK: false}
})
paths, err := svc.OpenFileDialog(OpenFileOptions{})
require.Error(t, err)
assert.Nil(t, paths)
}
func TestDisplayAPI_OpenFileDialog_Ugly(t *testing.T) {
svc, c := newTestDisplayAPIService(t)
c.Action("dialog.openFile", func(_ context.Context, _ core.Options) core.Result {
return core.Result{OK: true}
})
paths, err := svc.OpenFileDialog(OpenFileOptions{})
require.Error(t, err)
assert.Nil(t, paths)
}
func TestDisplayAPI_RequestNotificationPermission_BadType(t *testing.T) {
svc, c := newTestDisplayAPIService(t)
c.Action("notification.requestPermission", func(_ context.Context, _ core.Options) core.Result {
return core.Result{Value: "unexpected", OK: true}
})
granted, err := svc.RequestNotificationPermission()
require.Error(t, err)
assert.False(t, granted)
}
func TestDisplayAPI_GetTheme_Good(t *testing.T) {
svc, c := newTestDisplayAPIService(t)
c.RegisterQuery(func(_ *core.Core, q core.Query) core.Result {
switch q.(type) {
case environment.QueryTheme:
return core.Result{Value: environment.ThemeInfo{IsDark: true, Theme: "dark"}, OK: true}
default:
return core.Result{}
}
})
theme := svc.GetTheme()
require.NotNil(t, theme)
assert.True(t, theme.IsDark)
assert.Equal(t, "dark", svc.GetSystemTheme())
}
func TestDisplayAPI_GetTheme_Bad(t *testing.T) {
svc, c := newTestDisplayAPIService(t)
c.RegisterQuery(func(_ *core.Core, q core.Query) core.Result {
switch q.(type) {
case environment.QueryTheme:
return core.Result{Value: "unexpected", OK: true}
default:
return core.Result{}
}
})
theme := svc.GetTheme()
assert.Nil(t, theme)
assert.Empty(t, svc.GetSystemTheme())
}
func TestDisplayAPI_GetTheme_Ugly(t *testing.T) {
svc, c := newTestDisplayAPIService(t)
c.RegisterQuery(func(_ *core.Core, q core.Query) core.Result {
switch q.(type) {
case environment.QueryTheme:
return core.Result{OK: false}
default:
return core.Result{}
}
})
assert.Nil(t, svc.GetTheme())
assert.Empty(t, svc.GetSystemTheme())
}
func TestDisplayAPI_SaveFileDialog_Good(t *testing.T) {
svc, c := newTestDisplayAPIService(t)
c.Action("dialog.saveFile", func(_ context.Context, opts core.Options) core.Result {
task := opts.Get("task").Value.(dialog.TaskSaveFile)
assert.Equal(t, "Export", task.Options.Title)
assert.Equal(t, "/tmp", task.Options.Directory)
assert.Equal(t, "data.json", task.Options.Filename)
require.Len(t, task.Options.Filters, 1)
assert.Equal(t, "JSON", task.Options.Filters[0].DisplayName)
return core.Result{Value: "/exports/data.json", OK: true}
})
path, err := svc.SaveFileDialog(SaveFileOptions{
Title: "Export",
DefaultDirectory: "/tmp",
DefaultFilename: "data.json",
Filters: []FileFilter{{DisplayName: "JSON", Pattern: "*.json"}},
})
require.NoError(t, err)
assert.Equal(t, "/exports/data.json", path)
}
func TestDisplayAPI_SaveFileDialog_Bad(t *testing.T) {
svc, c := newTestDisplayAPIService(t)
c.Action("dialog.saveFile", func(_ context.Context, _ core.Options) core.Result {
return core.Result{Value: assert.AnError, OK: false}
})
path, err := svc.SaveFileDialog(SaveFileOptions{})
require.Error(t, err)
assert.Empty(t, path)
}
func TestDisplayAPI_SaveFileDialog_Ugly(t *testing.T) {
svc, c := newTestDisplayAPIService(t)
c.Action("dialog.saveFile", func(_ context.Context, _ core.Options) core.Result {
return core.Result{Value: 42, OK: true}
})
path, err := svc.SaveFileDialog(SaveFileOptions{})
require.Error(t, err)
assert.Empty(t, path)
}
func TestDisplayAPI_OpenDirectoryDialog_Good(t *testing.T) {
svc, c := newTestDisplayAPIService(t)
c.Action("dialog.openDirectory", func(_ context.Context, opts core.Options) core.Result {
task := opts.Get("task").Value.(dialog.TaskOpenDirectory)
assert.Equal(t, "Choose", task.Options.Title)
assert.Equal(t, "/var", task.Options.Directory)
return core.Result{Value: "/var/data", OK: true}
})
path, err := svc.OpenDirectoryDialog(OpenDirectoryOptions{
Title: "Choose",
DefaultDirectory: "/var",
})
require.NoError(t, err)
assert.Equal(t, "/var/data", path)
}
func TestDisplayAPI_OpenDirectoryDialog_Bad(t *testing.T) {
svc, c := newTestDisplayAPIService(t)
c.Action("dialog.openDirectory", func(_ context.Context, _ core.Options) core.Result {
return core.Result{Value: assert.AnError, OK: false}
})
path, err := svc.OpenDirectoryDialog(OpenDirectoryOptions{})
require.Error(t, err)
assert.Empty(t, path)
}
func TestDisplayAPI_OpenDirectoryDialog_Ugly(t *testing.T) {
svc, c := newTestDisplayAPIService(t)
c.Action("dialog.openDirectory", func(_ context.Context, _ core.Options) core.Result {
return core.Result{Value: 42, OK: true}
})
path, err := svc.OpenDirectoryDialog(OpenDirectoryOptions{})
require.Error(t, err)
assert.Empty(t, path)
}
func TestDisplayAPI_PromptDialog_Good(t *testing.T) {
svc, c := newTestDisplayAPIService(t)
c.Action("dialog.prompt", func(_ context.Context, opts core.Options) core.Result {
task := opts.Get("task").Value.(dialog.TaskPrompt)
assert.Equal(t, "Rename", task.Title)
assert.Equal(t, "Enter a new name", task.Message)
return core.Result{Value: dialog.PromptResult{Value: "draft", Confirmed: true}, OK: true}
})
value, confirmed, err := svc.PromptDialog("Rename", "Enter a new name")
require.NoError(t, err)
assert.True(t, confirmed)
assert.Equal(t, "draft", value)
}
func TestDisplayAPI_PromptDialog_Bad(t *testing.T) {
svc, c := newTestDisplayAPIService(t)
c.Action("dialog.prompt", func(_ context.Context, _ core.Options) core.Result {
return core.Result{Value: assert.AnError, OK: false}
})
value, confirmed, err := svc.PromptDialog("Rename", "Enter a new name")
require.Error(t, err)
assert.False(t, confirmed)
assert.Empty(t, value)
}
func TestDisplayAPI_PromptDialog_Ugly(t *testing.T) {
svc, c := newTestDisplayAPIService(t)
c.Action("dialog.prompt", func(_ context.Context, _ core.Options) core.Result {
return core.Result{Value: 42, OK: true}
})
value, confirmed, err := svc.PromptDialog("Rename", "Enter a new name")
require.Error(t, err)
assert.False(t, confirmed)
assert.Empty(t, value)
}
func TestDisplayAPI_ReadClipboardImage_Good(t *testing.T) {
svc, c := newTestDisplayAPIService(t)
payload := []byte{1, 2, 3}
c.RegisterQuery(func(_ *core.Core, q core.Query) core.Result {
switch q.(type) {
case clipboard.QueryImage:
return core.Result{Value: clipboard.ImageContent{Data: payload, HasImage: true}, OK: true}
default:
return core.Result{}
}
})
got, err := svc.ReadClipboardImage()
require.NoError(t, err)
require.Equal(t, []byte{1, 2, 3}, got)
payload[0] = 9
assert.Equal(t, byte(1), got[0])
}
func TestDisplayAPI_ReadClipboardImage_Bad(t *testing.T) {
svc, c := newTestDisplayAPIService(t)
c.RegisterQuery(func(_ *core.Core, q core.Query) core.Result {
switch q.(type) {
case clipboard.QueryImage:
return core.Result{Value: clipboard.ImageContent{HasImage: false}, OK: true}
default:
return core.Result{}
}
})
got, err := svc.ReadClipboardImage()
require.NoError(t, err)
assert.Nil(t, got)
}
func TestDisplayAPI_ReadClipboardImage_Ugly(t *testing.T) {
svc, c := newTestDisplayAPIService(t)
c.RegisterQuery(func(_ *core.Core, q core.Query) core.Result {
switch q.(type) {
case clipboard.QueryImage:
return core.Result{Value: "unexpected", OK: true}
default:
return core.Result{}
}
})
got, err := svc.ReadClipboardImage()
require.Error(t, err)
assert.Nil(t, got)
}
func TestDisplayAPI_ReadClipboardImage_Ugly_BackendFailure(t *testing.T) {
svc, c := newTestDisplayAPIService(t)
c.RegisterQuery(func(_ *core.Core, q core.Query) core.Result {
switch q.(type) {
case clipboard.QueryImage:
return core.Result{OK: false}
default:
return core.Result{}
}
})
got, err := svc.ReadClipboardImage()
require.Error(t, err)
assert.Nil(t, got)
}
func TestDisplayAPI_WriteClipboardImage_Good(t *testing.T) {
svc, c := newTestDisplayAPIService(t)
var got []byte
c.Action("clipboard.setImage", func(_ context.Context, opts core.Options) core.Result {
got = append([]byte(nil), opts.Get("data").Value.([]byte)...)
return core.Result{OK: true}
})
input := []byte{4, 5, 6}
err := svc.WriteClipboardImage(input)
require.NoError(t, err)
input[0] = 9
assert.True(t, bytes.Equal([]byte{4, 5, 6}, got))
}
func TestDisplayAPI_WriteClipboardImage_Bad(t *testing.T) {
svc, _ := newTestDisplayAPIService(t)
err := svc.WriteClipboardImage(nil)
require.Error(t, err)
}
func TestDisplayAPI_WriteClipboardImage_Ugly(t *testing.T) {
svc, c := newTestDisplayAPIService(t)
c.Action("clipboard.setImage", func(_ context.Context, _ core.Options) core.Result {
return core.Result{Value: assert.AnError, OK: false}
})
err := svc.WriteClipboardImage([]byte{1})
require.Error(t, err)
}