feat(ui): команды, snapshots, Fyne-окно и интеграция (--noui)
Команды (спец 12.2): - ui.Commands: действия UI → текстовые команды канала (start/cancel/skip/ retry/continue/approve/send), единый путь через chat.Channel. - ui.Window = chat.Channel + View; NilWindow для headless/тестов. Snapshots (спец 12.5): - ui.Store: чтение-модель, возвращает только копии (ListTasks/GetTask/ GetHistory/GetTraces); ui.DBStore поверх storage. Fyne-окно (internal/ui/desktop, build-tag cgo): - список задач слева, сплиты рабочей области и панели «Логи»/«Состояние», ввод+кнопки команд, тёмная тема, fullscreen, сохранение layout в Preferences, сворачивание при закрытии крестиком. - Колбэки View через fyne.Do (спец 12.4). Интеграция: - app.New(..., noUI); флаг --noui; UI собирается только с cgo (ui_cgo/ ui_noui фабрики), приложение headless без него. - Run: окно блокирует главную горутину; «Завершить» → cancel → graceful shutdown (спец 12.6). - go.mod: fyne.io/fyne/v2 v2.6.0 (direct).
This commit is contained in:
80
internal/ui/window_test.go
Normal file
80
internal/ui/window_test.go
Normal file
@@ -0,0 +1,80 @@
|
||||
package ui
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/kamelion/ratatoskr-go/internal/chat"
|
||||
)
|
||||
|
||||
func TestCommandsMapToText(t *testing.T) {
|
||||
var got []string
|
||||
c := NewCommands(func(text string) { got = append(got, text) })
|
||||
|
||||
c.Start()
|
||||
c.Cancel()
|
||||
c.Skip()
|
||||
c.Retry(7)
|
||||
c.Status(7)
|
||||
c.Continue(3)
|
||||
c.Approve()
|
||||
c.SendText("просто текст")
|
||||
|
||||
want := []string{"/start", "/cancel", "/skip", "/retry 7", "/status 7", "/continue 3", "создавай", "просто текст"}
|
||||
if len(got) != len(want) {
|
||||
t.Fatalf("got %d commands, want %d: %v", len(got), len(want), got)
|
||||
}
|
||||
for i := range want {
|
||||
if got[i] != want[i] {
|
||||
t.Errorf("command %d = %q, want %q", i, got[i], want[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNilWindowSubmitReachesHandler(t *testing.T) {
|
||||
w := NewNilWindow()
|
||||
|
||||
incoming := make(chan chat.Incoming, 1)
|
||||
w.OnMessage(func(inc chat.Incoming) {
|
||||
incoming <- inc
|
||||
})
|
||||
|
||||
w.Submit("/start")
|
||||
|
||||
select {
|
||||
case inc := <-incoming:
|
||||
if inc.UserID != UID {
|
||||
t.Errorf("UserID = %q, want %q", inc.UserID, UID)
|
||||
}
|
||||
if inc.Address != Address {
|
||||
t.Errorf("Address = %q, want %q", inc.Address, Address)
|
||||
}
|
||||
if inc.Msg.Text != "/start" {
|
||||
t.Errorf("Msg.Text = %q, want /start", inc.Msg.Text)
|
||||
}
|
||||
case <-time.After(2 * time.Second):
|
||||
t.Fatal("handler not called")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNilWindowRunStopsOnCancel(t *testing.T) {
|
||||
w := NewNilWindow()
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
done := make(chan error, 1)
|
||||
go func() { done <- w.Run(ctx) }()
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
cancel()
|
||||
select {
|
||||
case <-done:
|
||||
case <-time.After(2 * time.Second):
|
||||
t.Fatal("Run did not stop on cancel")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNilWindowSendNoop(t *testing.T) {
|
||||
w := NewNilWindow()
|
||||
if err := w.Send(context.Background(), Address, chat.Message{Text: "hi"}); err != nil {
|
||||
t.Fatalf("Send: %v", err)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user