From 74d429f47176bfb0713fd5c6ce212268aa91ec79 Mon Sep 17 00:00:00 2001 From: Snider Date: Wed, 15 Apr 2026 22:59:42 +0100 Subject: [PATCH] Add missing GUI unit coverage --- pkg/container/service_test.go | 215 ++++++++++++++++++++++++ pkg/container/tim_test.go | 299 ++++++++++++++++++++++++++++++++++ pkg/display/api_test.go | 271 ++++++++++++++++++++++++++++++ 3 files changed, 785 insertions(+) create mode 100644 pkg/container/service_test.go create mode 100644 pkg/container/tim_test.go create mode 100644 pkg/display/api_test.go diff --git a/pkg/container/service_test.go b/pkg/container/service_test.go new file mode 100644 index 00000000..bc4c4e35 --- /dev/null +++ b/pkg/container/service_test.go @@ -0,0 +1,215 @@ +package container + +import ( + "context" + "errors" + "testing" + "time" + + core "dappco.re/go/core" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func newTestContainerService(t *testing.T, options TIMOptions) (*Service, *core.Core) { + t.Helper() + + var svc *Service + c := core.New( + core.WithService(func(c *core.Core) core.Result { + svc = NewService(c, options) + return core.Result{Value: svc, OK: true} + }), + core.WithServiceLock(), + ) + require.True(t, c.ServiceStartup(context.Background(), nil).OK) + require.NotNil(t, svc) + return svc, c +} + +func TestService_OptionsFromEnv_Good(t *testing.T) { + t.Setenv("CORE_TIM_NAME", " worker ") + t.Setenv("CORE_TIM_IMAGE", " ghcr.io/example/tim:latest ") + t.Setenv("CORE_TIM_COMMAND", "run, --flag, , value ") + t.Setenv("CORE_TIM_DATA_DIR", " /var/lib/core-tim ") + + opts := OptionsFromEnv() + + assert.Equal(t, "worker", opts.Name) + assert.Equal(t, "ghcr.io/example/tim:latest", opts.Image) + assert.Equal(t, []string{"run", "--flag", "value"}, opts.Command) + assert.Equal(t, "/var/lib/core-tim", opts.DataDir) +} + +func TestService_OptionsFromEnv_Bad(t *testing.T) { + t.Setenv("CORE_TIM_NAME", "") + t.Setenv("CORE_TIM_IMAGE", "") + t.Setenv("CORE_TIM_COMMAND", "") + t.Setenv("CORE_TIM_DATA_DIR", "") + + opts := OptionsFromEnv() + + assert.Empty(t, opts.Name) + assert.Empty(t, opts.Image) + assert.Nil(t, opts.Command) + assert.Empty(t, opts.DataDir) +} + +func TestService_OptionsFromEnv_Ugly(t *testing.T) { + t.Setenv("CORE_TIM_NAME", " \t\n ") + t.Setenv("CORE_TIM_IMAGE", " \t ghcr.io/example/tim:latest \n") + t.Setenv("CORE_TIM_COMMAND", " , first ,, second , ") + t.Setenv("CORE_TIM_DATA_DIR", "\t /tmp/core-tim \n") + + opts := OptionsFromEnv() + + assert.Empty(t, opts.Name) + assert.Equal(t, "ghcr.io/example/tim:latest", opts.Image) + assert.Equal(t, []string{"first", "second"}, opts.Command) + assert.Equal(t, "/tmp/core-tim", opts.DataDir) +} + +func TestService_NewService_Good(t *testing.T) { + svc, _ := newTestContainerService(t, TIMOptions{ + Name: "coregui-tim", + Image: "ghcr.io/example/tim:latest", + Detect: func() ContainerRuntime { + return RuntimeDocker + }, + }) + + assert.NotNil(t, svc.manager) + state := svc.State() + assert.Equal(t, "coregui-tim", state.Name) + assert.Equal(t, "ghcr.io/example/tim:latest", state.Image) + assert.Equal(t, RuntimeDocker, state.Runtime) + assert.Equal(t, "stopped", state.Status) +} + +func TestService_NewService_Bad(t *testing.T) { + svc, _ := newTestContainerService(t, TIMOptions{ + Detect: func() ContainerRuntime { + return RuntimeNone + }, + }) + + state := svc.State() + assert.Equal(t, "coregui-tim", state.Name) + assert.Equal(t, "ghcr.io/lthn/core/tim:latest", state.Image) + assert.Equal(t, RuntimeNone, state.Runtime) +} + +func TestService_NewService_Ugly(t *testing.T) { + svc, _ := newTestContainerService(t, TIMOptions{ + Name: " worker node ", + Image: " ghcr.io/example/tim:edge ", + Command: []string{"alpha", "beta"}, + DataDir: " /tmp/data ", + Resources: TIMResources{ + CPUCores: 2, + MemoryMB: 512, + GPU: "all", + }, + Detect: func() ContainerRuntime { + return RuntimePodman + }, + }) + + state := svc.State() + assert.Equal(t, " worker node ", state.Name) + assert.Equal(t, " ghcr.io/example/tim:edge ", state.Image) + assert.Equal(t, []string{"alpha", "beta"}, state.Command) + assert.Equal(t, " /tmp/data ", state.DataDir) + assert.Equal(t, TIMResources{CPUCores: 2, MemoryMB: 512, GPU: "all"}, state.Resources) + assert.Equal(t, RuntimePodman, state.Runtime) +} + +func TestService_OnStartup_Good(t *testing.T) { + var calls []string + svc, c := newTestContainerService(t, TIMOptions{ + Name: "coregui-tim", + Image: "ghcr.io/example/tim:latest", + Command: []string{ + "sleep", + "1", + }, + Resources: TIMResources{CPUCores: 2, MemoryMB: 512, GPU: "all"}, + Detect: func() ContainerRuntime { + return RuntimeDocker + }, + Exec: func(_ context.Context, name string, args ...string) error { + calls = append(calls, append([]string{name}, args...)...) + return nil + }, + Now: func() time.Time { + return time.Unix(123, 0).UTC() + }, + }) + + runtime := c.Action("container.runtime.detect").Run(context.Background(), core.NewOptions()) + require.True(t, runtime.OK) + assert.Equal(t, RuntimeDocker, runtime.Value) + + status := c.Action("tim.status").Run(context.Background(), core.NewOptions()) + require.True(t, status.OK) + initial := status.Value.(TIMState) + assert.Equal(t, "stopped", initial.Status) + + started := c.Action("tim.start").Run(context.Background(), core.NewOptions()) + require.True(t, started.OK) + startState := started.Value.(TIMState) + assert.Equal(t, "running", startState.Status) + assert.Equal(t, time.Unix(123, 0).UTC(), startState.StartedAt) + require.NotEmpty(t, calls) + assert.Equal(t, "docker", calls[0]) + assert.Contains(t, calls, "run") + assert.Contains(t, calls, "--name") + assert.Contains(t, calls, "coregui-tim") + assert.Contains(t, calls, "--cpus") + assert.Contains(t, calls, "2") + assert.Contains(t, calls, "--memory") + assert.Contains(t, calls, "512m") + assert.Contains(t, calls, "--gpus") + assert.Contains(t, calls, "all") + + stopped := c.Action("tim.stop").Run(context.Background(), core.NewOptions()) + require.True(t, stopped.OK) + stopState := stopped.Value.(TIMState) + assert.Equal(t, "stopped", stopState.Status) + assert.Equal(t, "coregui-tim", svc.State().Name) +} + +func TestService_OnStartup_Bad(t *testing.T) { + _, c := newTestContainerService(t, TIMOptions{ + Detect: func() ContainerRuntime { + return RuntimeNone + }, + }) + + result := c.Action("tim.start").Run(context.Background(), core.NewOptions()) + + require.False(t, result.OK) + require.Error(t, result.Value.(error)) + assert.Contains(t, result.Value.(error).Error(), "no supported container runtime detected") +} + +func TestService_OnStartup_Ugly(t *testing.T) { + _, c := newTestContainerService(t, TIMOptions{ + Detect: func() ContainerRuntime { + return RuntimeDocker + }, + Exec: func(context.Context, string, ...string) error { + return errors.New("boom") + }, + }) + + result := c.Action("tim.start").Run(context.Background(), core.NewOptions()) + + require.False(t, result.OK) + require.Error(t, result.Value.(error)) + assert.Contains(t, result.Value.(error).Error(), "boom") + + status := c.Action("tim.status").Run(context.Background(), core.NewOptions()) + require.True(t, status.OK) + assert.Equal(t, "error", status.Value.(TIMState).Status) +} diff --git a/pkg/container/tim_test.go b/pkg/container/tim_test.go new file mode 100644 index 00000000..32a8f170 --- /dev/null +++ b/pkg/container/tim_test.go @@ -0,0 +1,299 @@ +package container + +import ( + "context" + "errors" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestTIMManager_NewTIMManager_Good(t *testing.T) { + manager := NewTIMManager(TIMOptions{ + Detect: func() ContainerRuntime { + return RuntimeDocker + }, + }) + + state := manager.State() + assert.Equal(t, "coregui-tim", state.Name) + assert.Equal(t, "ghcr.io/lthn/core/tim:latest", state.Image) + assert.Equal(t, RuntimeDocker, state.Runtime) + assert.Equal(t, "stopped", state.Status) + assert.Empty(t, state.StartedAt) +} + +func TestTIMManager_NewTIMManager_Bad(t *testing.T) { + manager := NewTIMManager(TIMOptions{ + Name: " ", + Image: " ", + Detect: func() ContainerRuntime { + return RuntimeNone + }, + }) + + state := manager.State() + assert.Equal(t, "coregui-tim", state.Name) + assert.Equal(t, "ghcr.io/lthn/core/tim:latest", state.Image) + assert.Equal(t, RuntimeNone, state.Runtime) + assert.Equal(t, "stopped", state.Status) +} + +func TestTIMManager_NewTIMManager_Ugly(t *testing.T) { + manager := NewTIMManager(TIMOptions{ + Name: "worker-node", + Image: "ghcr.io/example/tim:edge", + Command: []string{"alpha", "beta"}, + DataDir: "/var/lib/tim", + Runtime: RuntimePodman, + Resources: TIMResources{CPUCores: 2, MemoryMB: 512, GPU: "all"}, + Detect: func() ContainerRuntime { + return RuntimeDocker + }, + }) + + state := manager.State() + assert.Equal(t, "worker-node", state.Name) + assert.Equal(t, "ghcr.io/example/tim:edge", state.Image) + assert.Equal(t, RuntimePodman, state.Runtime) + assert.Equal(t, []string{"alpha", "beta"}, state.Command) + assert.Equal(t, "/var/lib/tim", state.DataDir) + assert.Equal(t, TIMResources{CPUCores: 2, MemoryMB: 512, GPU: "all"}, state.Resources) +} + +func TestTIMManager_Start_Good(t *testing.T) { + var calls []string + manager := NewTIMManager(TIMOptions{ + Name: "coregui-tim", + Image: "ghcr.io/example/tim:latest", + Command: []string{"sleep", "1"}, + Detect: func() ContainerRuntime { + return RuntimeDocker + }, + Exec: func(_ context.Context, name string, args ...string) error { + calls = append(calls, append([]string{name}, args...)...) + return nil + }, + Now: func() time.Time { + return time.Unix(456, 0).UTC() + }, + }) + + state, err := manager.Start(context.Background()) + require.NoError(t, err) + assert.Equal(t, "running", state.Status) + assert.Equal(t, time.Unix(456, 0).UTC(), state.StartedAt) + assert.Equal(t, "docker", calls[0]) + assert.Contains(t, calls, "run") + assert.Contains(t, calls, "--rm") + assert.Contains(t, calls, "--name") + assert.Contains(t, calls, "coregui-tim") + assert.Contains(t, calls, "ghcr.io/example/tim:latest") +} + +func TestTIMManager_Start_Bad(t *testing.T) { + manager := NewTIMManager(TIMOptions{ + Detect: func() ContainerRuntime { + return RuntimeNone + }, + }) + + state, err := manager.Start(context.Background()) + require.Error(t, err) + assert.Equal(t, RuntimeNone, state.Runtime) + assert.Equal(t, "stopped", state.Status) + assert.Contains(t, err.Error(), "no supported container runtime detected") +} + +func TestTIMManager_Start_Ugly(t *testing.T) { + manager := NewTIMManager(TIMOptions{ + Detect: func() ContainerRuntime { + return RuntimeDocker + }, + Exec: func(context.Context, string, ...string) error { + return errors.New("docker failed") + }, + }) + + state, err := manager.Start(context.Background()) + require.Error(t, err) + assert.Equal(t, "error", state.Status) + assert.Contains(t, err.Error(), "docker failed") +} + +func TestTIMManager_Stop_Good(t *testing.T) { + var calls []string + manager := NewTIMManager(TIMOptions{ + Detect: func() ContainerRuntime { + return RuntimeDocker + }, + Exec: func(_ context.Context, name string, args ...string) error { + calls = append(calls, append([]string{name}, args...)...) + return nil + }, + }) + + started, err := manager.Start(context.Background()) + require.NoError(t, err) + assert.Equal(t, "running", started.Status) + + stopped, err := manager.Stop(context.Background()) + require.NoError(t, err) + assert.Equal(t, "stopped", stopped.Status) + require.GreaterOrEqual(t, len(calls), 3) + assert.Equal(t, "docker", calls[len(calls)-3]) + assert.Equal(t, "stop", calls[len(calls)-2]) + assert.Equal(t, "coregui-tim", calls[len(calls)-1]) +} + +func TestTIMManager_Stop_Bad(t *testing.T) { + manager := NewTIMManager(TIMOptions{ + Detect: func() ContainerRuntime { + return RuntimeDocker + }, + Exec: func(context.Context, string, ...string) error { + return errors.New("stop failed") + }, + }) + + _, err := manager.Start(context.Background()) + require.Error(t, err) + + state, err := manager.Stop(context.Background()) + require.Error(t, err) + assert.Equal(t, "error", state.Status) + assert.Contains(t, err.Error(), "stop failed") +} + +func TestTIMManager_Stop_Ugly(t *testing.T) { + manager := NewTIMManager(TIMOptions{ + Detect: func() ContainerRuntime { + return RuntimeNone + }, + }) + + state, err := manager.Stop(context.Background()) + require.NoError(t, err) + assert.Equal(t, "stopped", state.Status) +} + +func TestTIMManager_runtimeCommand_Good(t *testing.T) { + cases := []struct { + name string + runtime ContainerRuntime + verb string + wantBin string + wantArgs []string + }{ + { + name: "docker run", + runtime: RuntimeDocker, + verb: "run", + wantBin: "docker", + wantArgs: []string{"run", "-d", "--rm", "--name", "tim"}, + }, + { + name: "apple stop", + runtime: RuntimeApple, + verb: "stop", + wantBin: "container", + wantArgs: []string{"stop", "tim"}, + }, + { + name: "podman run", + runtime: RuntimePodman, + verb: "run", + wantBin: "podman", + wantArgs: []string{"run", "-d", "--replace", "--name", "tim"}, + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + manager := NewTIMManager(TIMOptions{ + Name: "tim", + Image: "image", + Runtime: tc.runtime, + Detect: func() ContainerRuntime { + return RuntimeDocker + }, + }) + + gotBin, gotArgs := manager.runtimeCommand(tc.runtime, tc.verb) + assert.Equal(t, tc.wantBin, gotBin) + assert.Equal(t, tc.wantArgs, gotArgs[:len(tc.wantArgs)]) + if tc.verb == "run" { + assert.Contains(t, gotArgs, "image") + } + }) + } +} + +func TestTIMManager_runtimeCommand_Bad(t *testing.T) { + manager := NewTIMManager(TIMOptions{ + Name: "tim", + Image: "image", + Runtime: RuntimeNone, + Detect: func() ContainerRuntime { + return RuntimeDocker + }, + }) + + bin, args := manager.runtimeCommand(RuntimeNone, "run") + assert.Equal(t, "docker", bin) + assert.Contains(t, args, "--rm") +} + +func TestTIMManager_runtimeCommand_Ugly(t *testing.T) { + manager := NewTIMManager(TIMOptions{ + Name: "tim", + Image: "image", + Resources: TIMResources{ + CPUCores: 2, + MemoryMB: 512, + GPU: "all", + }, + Detect: func() ContainerRuntime { + return RuntimeDocker + }, + }) + + bin, args := manager.runtimeCommand(RuntimeDocker, "run") + require.Equal(t, "docker", bin) + assert.Contains(t, args, "--cpus") + assert.Contains(t, args, "2") + assert.Contains(t, args, "--memory") + assert.Contains(t, args, "512m") + assert.Contains(t, args, "--gpus") + assert.Contains(t, args, "all") +} + +func TestTIMManager_resourceArgs_Good(t *testing.T) { + assert.Nil(t, resourceArgs(TIMResources{})) +} + +func TestTIMManager_resourceArgs_Bad(t *testing.T) { + args := resourceArgs(TIMResources{CPUCores: 2}) + + assert.Equal(t, []string{"--cpus", "2"}, args) +} + +func TestTIMManager_resourceArgs_Ugly(t *testing.T) { + args := resourceArgs(TIMResources{CPUCores: 2, MemoryMB: 512, GPU: "all"}) + + assert.Equal(t, []string{"--cpus", "2", "--memory", "512m", "--gpus", "all"}, args) +} + +func TestTIMManager_coalesceRuntime_Good(t *testing.T) { + assert.Equal(t, RuntimeApple, coalesceRuntime(RuntimeApple, RuntimeDocker)) +} + +func TestTIMManager_coalesceRuntime_Bad(t *testing.T) { + assert.Equal(t, RuntimeDocker, coalesceRuntime(RuntimeNone, RuntimeDocker)) +} + +func TestTIMManager_coalesceRuntime_Ugly(t *testing.T) { + assert.Equal(t, RuntimeNone, coalesceRuntime(RuntimeNone, RuntimeNone)) +} diff --git a/pkg/display/api_test.go b/pkg/display/api_test.go new file mode 100644 index 00000000..2f517c7f --- /dev/null +++ b/pkg/display/api_test.go @@ -0,0 +1,271 @@ +package display + +import ( + "context" + "testing" + + core "dappco.re/go/core" + "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_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{} + } + }) + + assert.Nil(t, svc.GetScreens()) +} + +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{} + } + }) + + assert.Nil(t, svc.GetScreens()) +} + +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_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.NoError(t, err) + assert.Nil(t, paths) +} + +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() + require.NotNil(t, theme) + assert.False(t, theme.IsDark) + 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()) +}