228 lines
6.1 KiB
Go
228 lines
6.1 KiB
Go
package tray
|
|
|
|
import (
|
|
"errors"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
type fakeMenuItem struct {
|
|
clicked chan struct{}
|
|
title string
|
|
tooltip string
|
|
}
|
|
|
|
func (i *fakeMenuItem) Clicked() <-chan struct{} { return i.clicked }
|
|
func (i *fakeMenuItem) SetTitle(title string) { i.title = title }
|
|
func (i *fakeMenuItem) SetTooltip(tooltip string) { i.tooltip = tooltip }
|
|
|
|
type fakeBackend struct {
|
|
callbacks BackendCallbacks
|
|
icon []byte
|
|
tooltip string
|
|
items map[string]*fakeMenuItem
|
|
startErr error
|
|
iconErr error
|
|
menuErr error
|
|
starts int
|
|
stops int
|
|
}
|
|
|
|
func (b *fakeBackend) Start(callbacks BackendCallbacks) error {
|
|
b.starts++
|
|
b.callbacks = callbacks
|
|
return b.startErr
|
|
}
|
|
|
|
func (b *fakeBackend) SetIcon(icon []byte) error {
|
|
b.icon = append([]byte(nil), icon...)
|
|
return b.iconErr
|
|
}
|
|
|
|
func (b *fakeBackend) SetTooltip(tooltip string) error {
|
|
b.tooltip = tooltip
|
|
return nil
|
|
}
|
|
|
|
func (b *fakeBackend) AddMenuItem(title, tooltip string) (MenuItem, error) {
|
|
if b.menuErr != nil {
|
|
return nil, b.menuErr
|
|
}
|
|
item := &fakeMenuItem{clicked: make(chan struct{}, 1), title: title, tooltip: tooltip}
|
|
b.items[title] = item
|
|
return item, nil
|
|
}
|
|
|
|
func (b *fakeBackend) Stop() { b.stops++ }
|
|
|
|
func (b *fakeBackend) ready() {
|
|
if b.callbacks.Ready != nil {
|
|
b.callbacks.Ready()
|
|
}
|
|
}
|
|
|
|
func (b *fakeBackend) exited() {
|
|
if b.callbacks.Exit != nil {
|
|
b.callbacks.Exit()
|
|
}
|
|
}
|
|
|
|
func (b *fakeBackend) leftClick() {
|
|
if b.callbacks.LeftClick != nil {
|
|
b.callbacks.LeftClick()
|
|
}
|
|
}
|
|
|
|
func waitFor(t *testing.T, signal <-chan struct{}) {
|
|
t.Helper()
|
|
select {
|
|
case <-signal:
|
|
case <-time.After(time.Second):
|
|
t.Fatal("timed out waiting for tray action")
|
|
}
|
|
}
|
|
|
|
func TestControllerBecomesReadyOnlyAfterBackendCallback(t *testing.T) {
|
|
backend := &fakeBackend{items: make(map[string]*fakeMenuItem)}
|
|
controller := New(backend, []byte{1, 2, 3})
|
|
|
|
if err := controller.Start(Actions{}); err != nil {
|
|
t.Fatalf("Start() error = %v", err)
|
|
}
|
|
if controller.Ready() {
|
|
t.Fatal("tray became ready before backend callback")
|
|
}
|
|
|
|
backend.ready()
|
|
if !controller.Ready() {
|
|
t.Fatal("tray did not become ready after backend callback")
|
|
}
|
|
if string(backend.icon) != string([]byte{1, 2, 3}) || backend.tooltip != "Verstak" {
|
|
t.Fatalf("tray initialization = icon:%v tooltip:%q", backend.icon, backend.tooltip)
|
|
}
|
|
}
|
|
|
|
func TestControllerRoutesLeftClickAndMenuActionsToShowAndQuit(t *testing.T) {
|
|
backend := &fakeBackend{items: make(map[string]*fakeMenuItem)}
|
|
showCalls := make(chan struct{}, 2)
|
|
quitCalls := make(chan struct{}, 1)
|
|
controller := New(backend, []byte{1})
|
|
|
|
if err := controller.Start(Actions{
|
|
Show: func() { showCalls <- struct{}{} },
|
|
Quit: func() { quitCalls <- struct{}{} },
|
|
}); err != nil {
|
|
t.Fatalf("Start() error = %v", err)
|
|
}
|
|
backend.ready()
|
|
|
|
backend.leftClick()
|
|
waitFor(t, showCalls)
|
|
showItem := backend.items["Show Verstak"]
|
|
quitItem := backend.items["Quit"]
|
|
if showItem == nil || quitItem == nil {
|
|
t.Fatalf("tray menu = %#v, want Show Verstak and Quit", backend.items)
|
|
}
|
|
showItem.clicked <- struct{}{}
|
|
waitFor(t, showCalls)
|
|
quitItem.clicked <- struct{}{}
|
|
waitFor(t, quitCalls)
|
|
}
|
|
|
|
func TestControllerStopCallsBackendOnce(t *testing.T) {
|
|
backend := &fakeBackend{items: make(map[string]*fakeMenuItem)}
|
|
controller := New(backend, []byte{1})
|
|
if err := controller.Start(Actions{}); err != nil {
|
|
t.Fatalf("Start() error = %v", err)
|
|
}
|
|
|
|
controller.Stop()
|
|
controller.Stop()
|
|
|
|
if backend.stops != 1 {
|
|
t.Fatalf("backend stop calls = %d, want 1", backend.stops)
|
|
}
|
|
}
|
|
|
|
func TestControllerStartupAndSetupFailuresNeverBecomeReady(t *testing.T) {
|
|
for name, backend := range map[string]*fakeBackend{
|
|
"start": {items: make(map[string]*fakeMenuItem), startErr: errors.New("start failed")},
|
|
"icon": {items: make(map[string]*fakeMenuItem), iconErr: errors.New("icon failed")},
|
|
"menu": {items: make(map[string]*fakeMenuItem), menuErr: errors.New("menu failed")},
|
|
} {
|
|
t.Run(name, func(t *testing.T) {
|
|
controller := New(backend, []byte{1})
|
|
err := controller.Start(Actions{})
|
|
if name == "start" && err == nil {
|
|
t.Fatal("Start() error = nil, want startup failure")
|
|
}
|
|
if name != "start" && err != nil {
|
|
t.Fatalf("Start() error = %v, want nil before ready callback", err)
|
|
}
|
|
backend.ready()
|
|
if controller.Ready() {
|
|
t.Fatal("failed tray became ready")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestControllerExitRevokesReadinessAndKeepsLocalizedMenuItems(t *testing.T) {
|
|
backend := &fakeBackend{items: make(map[string]*fakeMenuItem)}
|
|
controller := New(backend, []byte{1})
|
|
controller.SetLabels(LabelsForPreference("ru"))
|
|
if err := controller.Start(Actions{}); err != nil {
|
|
t.Fatalf("Start() error = %v", err)
|
|
}
|
|
backend.ready()
|
|
|
|
show := backend.items["Показать Верстак"]
|
|
quit := backend.items["Выйти"]
|
|
if show == nil || quit == nil {
|
|
t.Fatalf("Russian tray menu = %#v", backend.items)
|
|
}
|
|
controller.SetLabels(LabelsForPreference("en"))
|
|
if show.title != "Show Verstak" || quit.title != "Quit" {
|
|
t.Fatalf("English tray menu after update = show:%q quit:%q", show.title, quit.title)
|
|
}
|
|
backend.exited()
|
|
if controller.Ready() {
|
|
t.Fatal("tray stayed ready after backend exit")
|
|
}
|
|
}
|
|
|
|
func TestLabelsForSystemRussianLocale(t *testing.T) {
|
|
labels := LabelsForPreference("system", "", "ru_RU.UTF-8", "en_US.UTF-8")
|
|
if labels.ShowTitle != "Показать Верстак" || labels.QuitTitle != "Выйти" {
|
|
t.Fatalf("system Russian labels = %#v", labels)
|
|
}
|
|
labels = LabelsForPreference("system", "C.UTF-8", "", "ru_RU.UTF-8")
|
|
if labels.ShowTitle != "Show Verstak" || labels.QuitTitle != "Quit" {
|
|
t.Fatalf("higher-priority system locale must win, got %#v", labels)
|
|
}
|
|
}
|
|
|
|
func TestControllerReadyNotificationIsSafeAcrossCallbacks(t *testing.T) {
|
|
backend := &fakeBackend{items: make(map[string]*fakeMenuItem)}
|
|
controller := New(backend, []byte{1})
|
|
var changes []bool
|
|
var mu sync.Mutex
|
|
controller.SetReadyChangedHandler(func(ready bool) {
|
|
mu.Lock()
|
|
changes = append(changes, ready)
|
|
mu.Unlock()
|
|
})
|
|
if err := controller.Start(Actions{}); err != nil {
|
|
t.Fatalf("Start() error = %v", err)
|
|
}
|
|
backend.ready()
|
|
backend.exited()
|
|
|
|
mu.Lock()
|
|
defer mu.Unlock()
|
|
if len(changes) != 2 || !changes[0] || changes[1] {
|
|
t.Fatalf("ready changes = %#v, want [true false]", changes)
|
|
}
|
|
}
|