aboutsummaryrefslogtreecommitdiffstats
path: root/tui/tui_test.go
blob: d2d2e5f898e02dee8b902b0e26bd2ad405e674c4 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
package tui

import (
	"fmt"
	"path/filepath"
	"strings"
	"testing"

	"adammathes.com/neko/config"
	"adammathes.com/neko/models"
	"adammathes.com/neko/models/feed"
	"adammathes.com/neko/models/item"
	tea "github.com/charmbracelet/bubbletea"
)

func setupTestDB(t *testing.T) {
	t.Helper()
	config.Config.DBFile = filepath.Join(t.TempDir(), "test.db")
	models.InitDB()
	t.Cleanup(func() {
		if models.DB != nil {
			models.DB.Close()
		}
	})
}

func seedData(t *testing.T) {
	t.Helper()
	f := &feed.Feed{Url: "http://example.com", Title: "Test Feed", Category: "tech"}
	f.Create()

	i := &item.Item{
		Title:  "Test Item",
		Url:    "http://example.com/1",
		FeedId: f.Id,
	}
	i.Create()
}

func TestNewModel(t *testing.T) {
	m := NewModel()
	if m.state != sidebarFocus {
		t.Errorf("Expected initial state sidebarFocus, got %v", m.state)
	}
}

func TestUpdateWindowSizeNoPanic(t *testing.T) {
	m := NewModel()
	msg := tea.WindowSizeMsg{Width: 80, Height: 24}
	newModel, _ := m.Update(msg)
	tm := newModel.(Model)
	if tm.width != 80 || tm.height != 24 {
		t.Errorf("Expected size 80x24, got %dx%d", tm.width, tm.height)
	}
	if !tm.ready {
		t.Error("Model should be ready after WindowSizeMsg")
	}
}

func TestStateTransitions(t *testing.T) {
	setupTestDB(t)
	m := NewModel()
	m1, _ := m.Update(tea.WindowSizeMsg{Width: 80, Height: 24})
	m = m1.(Model)

	// Feed loaded
	feeds := []*feed.Feed{{Id: 1, Title: "Test Feed"}}
	m2, _ := m.Update(feedsMsg(feeds))
	tm2 := m2.(Model)
	if len(tm2.feedData) != 1 {
		t.Fatal("Expected 1 feed")
	}

	// Items loaded
	items := []*item.Item{{Id: 1, Title: "Test Item", Description: "Desc"}}
	// Simulate switching to item focus via Enter (which triggers loadItems)
	// But explicitly setting state for unit test
	tm2.state = sidebarFocus // Ensure we are in sidebar
	// Update with itemsMsg
	m3, _ := tm2.Update(itemsMsg(items))
	tm3 := m3.(Model)

	// In the new implementation, loading items doesn't auto-switch focus unless logic says so.
	// But let's check if the items are populated.
	if len(tm3.itemData) != 1 {
		t.Fatal("Expected 1 item")
	}

	// Manually switch focus to items (simulating Tab or logic)
	tm3.state = itemFocus

	// Test entering content view
	// Needs selection first. In unit test, list selection might be 0 by default but items need to be set in list model.
	// The list model update happens in Update command usually, but here we just updated data.
	// We need to sync list items.
	// In Update(itemsMsg), we do `m.items.SetItems(...)`. So list should have items.

	// Select item 0
	tm3.items.Select(0)

	m4, _ := tm3.Update(tea.KeyMsg{Type: tea.KeyEnter})
	tm4m := m4.(Model)
	if tm4m.state != contentFocus {
		t.Errorf("Expected state contentFocus, got %v", tm4m.state)
	}
	if !strings.Contains(tm4m.content.View(), "Test Item") {
		t.Error("Expected content view to show item title")
	}

	// Back from content to items
	m5, _ := tm4m.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune("q")})
	tm5m := m5.(Model)
	if tm5m.state != itemFocus {
		t.Errorf("Expected back to itemFocus, got %v", tm5m.state)
	}
}

func TestTuiCommands(t *testing.T) {
	setupTestDB(t)
	seedData(t)

	m := NewModel()
	cmd := m.Init()
	if cmd == nil {
		t.Fatal("Init should return a command")
	}

	msg := loadFeeds()
	feeds, ok := msg.(feedsMsg)
	if !ok || len(feeds) == 0 {
		t.Errorf("loadFeeds should return feedsMsg, got %T", msg)
	}

	msg2 := loadItems(feeds[0].Id)()
	_, ok = msg2.(itemsMsg)
	if !ok {
		t.Errorf("loadItems should return itemsMsg, got %T", msg2)
	}
}

func TestUpdateError(t *testing.T) {
	m := NewModel()
	msg := errMsg(fmt.Errorf("test error"))
	newModel, cmd := m.Update(msg)
	tm := newModel.(Model)
	if tm.err == nil {
		t.Error("Expected error to be set in model")
	}
	if cmd == nil {
		t.Error("Expected tea.Quit command (non-nil)")
	}
}

func TestSwitchFocus(t *testing.T) {
	m := NewModel()
	m.state = sidebarFocus

	// Tab to switch
	m1, _ := m.Update(tea.KeyMsg{Type: tea.KeyTab})
	if m1.(Model).state != itemFocus {
		t.Error("Tab from sidebar should go to itemFocus")
	}

	m2, _ := m1.Update(tea.KeyMsg{Type: tea.KeyTab})
	if m2.(Model).state != sidebarFocus {
		t.Error("Tab from itemFocus should go back to sidebarFocus")
	}
}

func TestView(t *testing.T) {
	m := NewModel()
	// Trigger WindowSizeMsg to make it ready and size components
	m1, _ := m.Update(tea.WindowSizeMsg{Width: 80, Height: 24})
	tm := m1.(Model)

	v := tm.View()
	// It should render "Feeds" and "Items" (titles of lists)
	if !strings.Contains(v, "Feeds") {
		t.Error("View should contain Feeds")
	}
	if !strings.Contains(v, "Items") {
		t.Error("View should contain Items")
	}
}