Files
lazyntfy/internal/tui/model_test.go
T
ahp 8673f1e83a feat(tui): add the lazygit-style three-pane interface
Topics, messages, and message detail in three panes, with filtering, publishing, adding topics, a raw JSON view, history restore, and open-link support.
2026-10-11 12:04:53 +03:30

140 lines
3.9 KiB
Go

package tui
import (
"path/filepath"
"strings"
"testing"
tea "charm.land/bubbletea/v2"
"lazyntfy/internal/config"
"lazyntfy/internal/history"
"lazyntfy/internal/ntfy"
"lazyntfy/internal/store"
)
func sample() Model {
m := New(config.App{
DefaultHost: "https://ntfy.example",
Auth: config.Auth{Token: "tk_test"},
Subs: []config.Sub{{Origin: "https://ntfy.example", Name: "alerts", Auth: config.Auth{Token: "tk_test"}}},
}, nil, ntfy.NewClient(), nil, nil)
m.width = 120
m.height = 30
m.store.Put(store.Message{ID: "new", Time: 200, Topic: "alerts", Title: "DiskFull", Body: `{"status":"firing","commonLabels":{"alertname":"DiskFull","severity":"critical"}}`})
m.store.Put(store.Message{ID: "old", Time: 100, Topic: "alerts", Body: "backup finished"})
m = m.ensureSelection()
return m
}
func TestViewShowsNewestAlert(t *testing.T) {
view := sample().render()
if !strings.Contains(view, "lazyntfy") || !strings.Contains(view, "DiskFull") || !strings.Contains(view, "firing") {
t.Fatalf("%s", view)
}
lines := strings.Split(view, "\n")
if len(lines) != 30 {
t.Fatalf("height %d", len(lines))
}
}
func TestMoveKeepsSelectionByID(t *testing.T) {
m := sample()
if m.selected["alerts"] != "new" {
t.Fatalf("selected %s", m.selected["alerts"])
}
m = m.move(1)
if m.selected["alerts"] != "old" {
t.Fatalf("after down %s", m.selected["alerts"])
}
next, cmd := m.Update(tea.KeyPressMsg{Text: "q", Code: 'q'})
if cmd == nil {
t.Fatal("expected quit")
}
if next.(Model).selected["alerts"] != "old" {
t.Fatal("quit changed selection")
}
}
func TestFilterAndDelete(t *testing.T) {
m := sample()
m.filter = "backup"
m = m.ensureSelection()
if m.selected["alerts"] != "old" {
t.Fatalf("filter selected %s", m.selected["alerts"])
}
if !strings.Contains(m.render(), "backup finished") {
t.Fatal(m.render())
}
m = m.apply(ntfy.Notice{Event: &ntfy.Event{ID: "old", Event: "message_delete", Topic: "alerts"}})
if len(m.store.List("alerts")) != 1 {
t.Fatal("delete")
}
m = m.apply(ntfy.Notice{Event: &ntfy.Event{Event: "message_clear", Topic: "alerts"}})
if len(m.store.List("alerts")) != 0 {
t.Fatal("clear")
}
}
func TestPublishRequiresMessage(t *testing.T) {
m := sample()
m.mode = modePublish
m.formIdx = 3
got, _ := m.updatePublish(tea.KeyPressMsg{Text: "enter", Code: tea.KeyEnter})
if got.(Model).status != "message is empty" {
t.Fatalf("status %q", got.(Model).status)
}
}
func TestAddTopicUsesDefaultHost(t *testing.T) {
m := sample()
m.addInput.SetValue("backups")
m = m.submitAdd()
if len(m.subs) != 2 || m.subs[1].Name != "backups" || m.subs[1].Origin != "https://ntfy.example" {
t.Fatalf("%+v", m.subs)
}
if m.subs[1].Auth.Token != "tk_test" {
t.Fatal("auth not copied")
}
}
func TestRejectsNonHTTPClick(t *testing.T) {
if err := validateURL("file:///etc/passwd"); err == nil {
t.Fatal("expected reject")
}
if err := validateURL("https://example.com/a"); err != nil {
t.Fatal(err)
}
}
func TestRestoresMessagesAndTopicFromHistory(t *testing.T) {
db, err := history.Open(filepath.Join(t.TempDir(), "history.db"))
if err != nil {
t.Fatal(err)
}
defer db.Close()
if err := db.SaveMessage("https://ntfy.example", store.Message{
ID: "h1", Topic: "alerts", Time: 50, Title: "from disk",
}); err != nil {
t.Fatal(err)
}
if err := db.SetState("topic", "alerts"); err != nil {
t.Fatal(err)
}
m := New(config.App{
DefaultHost: "https://ntfy.example",
Subs: []config.Sub{{Origin: "https://ntfy.example", Name: "alerts"}},
}, nil, ntfy.NewClient(), nil, db)
if m.restored != 1 || len(m.store.List("alerts")) != 1 {
t.Fatalf("restored %d messages %d", m.restored, len(m.store.List("alerts")))
}
if m.savedTopic != "alerts" || m.currentName() != "alerts" {
t.Fatalf("saved %q current %q", m.savedTopic, m.currentName())
}
m.width, m.height = 120, 30
if !strings.Contains(m.render(), "from disk") {
t.Fatalf("restored message missing from view:\n%s", m.render())
}
}