feat: 增强桌面启动失败提示与测试覆盖
This commit is contained in:
169
backend/cmd/desktop/tray_test.go
Normal file
169
backend/cmd/desktop/tray_test.go
Normal file
@@ -0,0 +1,169 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type fakeTrayController struct {
|
||||
run func(onReady func(), onExit func())
|
||||
|
||||
quitCh chan struct{}
|
||||
quitOnce sync.Once
|
||||
|
||||
icon []byte
|
||||
tooltip string
|
||||
menuItems []*fakeTrayMenuItem
|
||||
}
|
||||
|
||||
func newFakeTrayController() *fakeTrayController {
|
||||
return &fakeTrayController{quitCh: make(chan struct{})}
|
||||
}
|
||||
|
||||
func (c *fakeTrayController) Run(onReady func(), onExit func()) {
|
||||
if c.run != nil {
|
||||
c.run(onReady, onExit)
|
||||
return
|
||||
}
|
||||
onReady()
|
||||
<-c.quitCh
|
||||
if onExit != nil {
|
||||
onExit()
|
||||
}
|
||||
}
|
||||
|
||||
func (c *fakeTrayController) Quit() {
|
||||
c.quitOnce.Do(func() { close(c.quitCh) })
|
||||
}
|
||||
|
||||
func (c *fakeTrayController) SetIcon(icon []byte) {
|
||||
c.icon = append([]byte(nil), icon...)
|
||||
}
|
||||
|
||||
func (c *fakeTrayController) SetTooltip(tooltip string) {
|
||||
c.tooltip = tooltip
|
||||
}
|
||||
|
||||
func (c *fakeTrayController) AddMenuItem(title, tooltip string) trayMenuItem {
|
||||
item := &fakeTrayMenuItem{clicked: make(chan struct{}), title: title, tooltip: tooltip}
|
||||
c.menuItems = append(c.menuItems, item)
|
||||
return item
|
||||
}
|
||||
|
||||
func (c *fakeTrayController) AddSeparator() {}
|
||||
|
||||
type fakeTrayMenuItem struct {
|
||||
clicked chan struct{}
|
||||
title string
|
||||
tooltip string
|
||||
disabled bool
|
||||
}
|
||||
|
||||
func (m *fakeTrayMenuItem) Disable() {
|
||||
m.disabled = true
|
||||
}
|
||||
|
||||
func (m *fakeTrayMenuItem) Clicked() <-chan struct{} {
|
||||
return m.clicked
|
||||
}
|
||||
|
||||
func TestRunSystrayReadyOpensBrowser(t *testing.T) {
|
||||
controller := newFakeTrayController()
|
||||
opened := make(chan string, 1)
|
||||
|
||||
err := runSystray(19826, trayOptions{
|
||||
controller: controller,
|
||||
readyTimeout: time.Second,
|
||||
iconLoader: func() ([]byte, error) { return []byte("icon"), nil },
|
||||
openBrowser: func(url string) error {
|
||||
opened <- url
|
||||
controller.Quit()
|
||||
return nil
|
||||
},
|
||||
notify: func(string, string) {},
|
||||
logger: zap.NewNop(),
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("托盘 ready 成功不应返回错误: %v", err)
|
||||
}
|
||||
|
||||
if got := <-opened; got != "http://localhost:19826" {
|
||||
t.Fatalf("浏览器 URL = %s", got)
|
||||
}
|
||||
if string(controller.icon) != "icon" {
|
||||
t.Fatalf("应设置托盘图标")
|
||||
}
|
||||
if controller.tooltip != appTooltip {
|
||||
t.Fatalf("tooltip = %q, want %q", controller.tooltip, appTooltip)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunSystrayReadyTimeoutReturnsTrayStartupError(t *testing.T) {
|
||||
controller := newFakeTrayController()
|
||||
controller.run = func(_ func(), _ func()) {
|
||||
<-controller.quitCh
|
||||
}
|
||||
|
||||
err := runSystray(19826, trayOptions{
|
||||
controller: controller,
|
||||
readyTimeout: 10 * time.Millisecond,
|
||||
iconLoader: func() ([]byte, error) { return []byte("icon"), nil },
|
||||
openBrowser: func(string) error { return nil },
|
||||
notify: func(string, string) {},
|
||||
logger: zap.NewNop(),
|
||||
})
|
||||
if err == nil {
|
||||
t.Fatal("托盘 ready timeout 应返回错误")
|
||||
}
|
||||
var startupErr *startupError
|
||||
if !errors.As(err, &startupErr) || startupErr.Phase() != "tray" {
|
||||
t.Fatalf("应返回 tray 阶段启动错误,实际: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunSystrayIconLoadFailureReturnsTrayStartupError(t *testing.T) {
|
||||
controller := newFakeTrayController()
|
||||
|
||||
err := runSystray(19826, trayOptions{
|
||||
controller: controller,
|
||||
readyTimeout: time.Second,
|
||||
iconLoader: func() ([]byte, error) { return nil, errors.New("missing icon") },
|
||||
openBrowser: func(string) error { return nil },
|
||||
notify: func(string, string) {},
|
||||
logger: zap.NewNop(),
|
||||
})
|
||||
if err == nil {
|
||||
t.Fatal("托盘图标加载失败应返回错误")
|
||||
}
|
||||
var startupErr *startupError
|
||||
if !errors.As(err, &startupErr) || startupErr.Phase() != "tray" {
|
||||
t.Fatalf("应返回 tray 阶段启动错误,实际: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunSystrayBrowserOpenFailureIsNonFatal(t *testing.T) {
|
||||
controller := newFakeTrayController()
|
||||
notified := make(chan string, 1)
|
||||
|
||||
err := runSystray(19826, trayOptions{
|
||||
controller: controller,
|
||||
readyTimeout: time.Second,
|
||||
iconLoader: func() ([]byte, error) { return []byte("icon"), nil },
|
||||
openBrowser: func(string) error { return errors.New("no browser") },
|
||||
notify: func(_, message string) {
|
||||
notified <- message
|
||||
controller.Quit()
|
||||
},
|
||||
logger: zap.NewNop(),
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("浏览器打开失败不应成为 fatal: %v", err)
|
||||
}
|
||||
if got := <-notified; got == "" {
|
||||
t.Fatal("浏览器打开失败应提示用户")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user