1package tui
2
3import (
4 "testing"
5 "time"
6
7 tea "github.com/charmbracelet/bubbletea"
8 "github.com/floatpane/matcha/config"
9 "github.com/floatpane/matcha/fetcher"
10)
11
12// TestInboxUpdate verifies the state transitions in the inbox view.
13func TestInboxUpdate(t *testing.T) {
14 // Create sample accounts
15 accounts := []config.Account{
16 {ID: "account-1", Email: "test1@example.com", Name: "Test User 1"},
17 {ID: "account-2", Email: "test2@example.com", Name: "Test User 2"},
18 }
19
20 // Create a sample list of emails.
21 sampleEmails := []fetcher.Email{
22 {UID: 1, From: "a@example.com", Subject: "Email 1", Date: time.Now(), AccountID: "account-1"},
23 {UID: 2, From: "b@example.com", Subject: "Email 2", Date: time.Now().Add(-time.Hour), AccountID: "account-1"},
24 {UID: 3, From: "c@example.com", Subject: "Email 3", Date: time.Now().Add(-2 * time.Hour), AccountID: "account-2"},
25 }
26
27 inbox := NewInbox(sampleEmails, accounts)
28
29 t.Run("Select email to view", func(t *testing.T) {
30 // By default, the first item is selected (index 0).
31 // Move down to the second item (index 1).
32 inbox.list, _ = inbox.list.Update(tea.KeyMsg{Type: tea.KeyDown})
33
34 // Simulate pressing Enter to view the selected email.
35 _, cmd := inbox.Update(tea.KeyMsg{Type: tea.KeyEnter})
36 if cmd == nil {
37 t.Fatal("Expected a command, but got nil.")
38 }
39
40 // Check the resulting message.
41 msg := cmd()
42 viewMsg, ok := msg.(ViewEmailMsg)
43 if !ok {
44 t.Fatalf("Expected a ViewEmailMsg, but got %T", msg)
45 }
46
47 // The index should match the selected item in the list.
48 expectedIndex := 1
49 if viewMsg.Index != expectedIndex {
50 t.Errorf("Expected index %d, but got %d", expectedIndex, viewMsg.Index)
51 }
52
53 // Verify UID and AccountID are passed correctly
54 expectedUID := uint32(2) // Second email has UID 2
55 if viewMsg.UID != expectedUID {
56 t.Errorf("Expected UID %d, but got %d", expectedUID, viewMsg.UID)
57 }
58
59 expectedAccountID := "account-1" // Second email belongs to account-1
60 if viewMsg.AccountID != expectedAccountID {
61 t.Errorf("Expected AccountID %q, but got %q", expectedAccountID, viewMsg.AccountID)
62 }
63 })
64}
65
66// TestInboxMultiAccountTabs verifies that tabs are created for multiple accounts.
67func TestInboxMultiAccountTabs(t *testing.T) {
68 accounts := []config.Account{
69 {ID: "account-1", Email: "test1@example.com", Name: "User 1"},
70 {ID: "account-2", Email: "test2@example.com", Name: "User 2"},
71 }
72
73 emails := []fetcher.Email{
74 {UID: 1, From: "sender@example.com", Subject: "Test", AccountID: "account-1"},
75 }
76
77 inbox := NewInbox(emails, accounts)
78
79 // Should have 3 tabs: ALL + 2 accounts
80 if len(inbox.tabs) != 3 {
81 t.Errorf("Expected 3 tabs, got %d", len(inbox.tabs))
82 }
83
84 // First tab should be "ALL"
85 if inbox.tabs[0].ID != "" {
86 t.Errorf("Expected first tab ID to be empty (ALL), got %q", inbox.tabs[0].ID)
87 }
88 if inbox.tabs[0].Label != "ALL" {
89 t.Errorf("Expected first tab label to be 'ALL', got %q", inbox.tabs[0].Label)
90 }
91}
92
93// TestInboxSingleAccount verifies behavior with a single account.
94func TestInboxSingleAccount(t *testing.T) {
95 accounts := []config.Account{
96 {ID: "account-1", Email: "test@example.com"},
97 }
98
99 emails := []fetcher.Email{
100 {UID: 1, From: "sender@example.com", Subject: "Test", AccountID: "account-1"},
101 }
102
103 inbox := NewInbox(emails, accounts)
104
105 // Should have 2 tabs: ALL + 1 account
106 if len(inbox.tabs) != 2 {
107 t.Errorf("Expected 2 tabs, got %d", len(inbox.tabs))
108 }
109}
110
111// TestInboxNoAccounts verifies behavior with no accounts (legacy/edge case).
112func TestInboxNoAccounts(t *testing.T) {
113 emails := []fetcher.Email{
114 {UID: 1, From: "sender@example.com", Subject: "Test"},
115 }
116
117 inbox := NewInbox(emails, nil)
118
119 // Should have 1 tab: ALL only
120 if len(inbox.tabs) != 1 {
121 t.Errorf("Expected 1 tab, got %d", len(inbox.tabs))
122 }
123}
124
125// TestInboxDeleteEmailMsg verifies that delete messages include account ID.
126func TestInboxDeleteEmailMsg(t *testing.T) {
127 accounts := []config.Account{
128 {ID: "account-1", Email: "test@example.com"},
129 }
130
131 emails := []fetcher.Email{
132 {UID: 123, From: "sender@example.com", Subject: "Test", AccountID: "account-1"},
133 }
134
135 inbox := NewInbox(emails, accounts)
136
137 // Simulate pressing 'd' to delete
138 _, cmd := inbox.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'d'}})
139 if cmd == nil {
140 t.Fatal("Expected a command, but got nil.")
141 }
142
143 msg := cmd()
144 deleteMsg, ok := msg.(DeleteEmailMsg)
145 if !ok {
146 t.Fatalf("Expected a DeleteEmailMsg, but got %T", msg)
147 }
148
149 if deleteMsg.UID != 123 {
150 t.Errorf("Expected UID 123, got %d", deleteMsg.UID)
151 }
152
153 if deleteMsg.AccountID != "account-1" {
154 t.Errorf("Expected AccountID 'account-1', got %q", deleteMsg.AccountID)
155 }
156}
157
158// TestInboxArchiveEmailMsg verifies that archive messages include account ID.
159func TestInboxArchiveEmailMsg(t *testing.T) {
160 accounts := []config.Account{
161 {ID: "account-1", Email: "test@example.com"},
162 }
163
164 emails := []fetcher.Email{
165 {UID: 456, From: "sender@example.com", Subject: "Test", AccountID: "account-1"},
166 }
167
168 inbox := NewInbox(emails, accounts)
169
170 // Simulate pressing 'a' to archive
171 _, cmd := inbox.Update(tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'a'}})
172 if cmd == nil {
173 t.Fatal("Expected a command, but got nil.")
174 }
175
176 msg := cmd()
177 archiveMsg, ok := msg.(ArchiveEmailMsg)
178 if !ok {
179 t.Fatalf("Expected an ArchiveEmailMsg, but got %T", msg)
180 }
181
182 if archiveMsg.UID != 456 {
183 t.Errorf("Expected UID 456, got %d", archiveMsg.UID)
184 }
185
186 if archiveMsg.AccountID != "account-1" {
187 t.Errorf("Expected AccountID 'account-1', got %q", archiveMsg.AccountID)
188 }
189}
190
191// TestInboxRemoveEmail verifies that emails can be removed from the inbox.
192func TestInboxRemoveEmail(t *testing.T) {
193 accounts := []config.Account{
194 {ID: "account-1", Email: "test@example.com"},
195 }
196
197 emails := []fetcher.Email{
198 {UID: 1, From: "a@example.com", Subject: "Email 1", AccountID: "account-1"},
199 {UID: 2, From: "b@example.com", Subject: "Email 2", AccountID: "account-1"},
200 }
201
202 inbox := NewInbox(emails, accounts)
203
204 // Remove the first email
205 inbox.RemoveEmail(1, "account-1")
206
207 // Check that only one email remains
208 if len(inbox.allEmails) != 1 {
209 t.Errorf("Expected 1 email after removal, got %d", len(inbox.allEmails))
210 }
211
212 if inbox.allEmails[0].UID != 2 {
213 t.Errorf("Expected remaining email UID to be 2, got %d", inbox.allEmails[0].UID)
214 }
215}
216
217// TestInboxGetEmailAtIndex verifies retrieving emails by index.
218func TestInboxGetEmailAtIndex(t *testing.T) {
219 accounts := []config.Account{
220 {ID: "account-1", Email: "test@example.com"},
221 }
222
223 emails := []fetcher.Email{
224 {UID: 1, From: "a@example.com", Subject: "Email 1", AccountID: "account-1"},
225 {UID: 2, From: "b@example.com", Subject: "Email 2", AccountID: "account-1"},
226 }
227
228 inbox := NewInbox(emails, accounts)
229
230 // Get email at index 0
231 email := inbox.GetEmailAtIndex(0)
232 if email == nil {
233 t.Fatal("Expected email at index 0, got nil")
234 }
235 if email.UID != 1 {
236 t.Errorf("Expected UID 1 at index 0, got %d", email.UID)
237 }
238
239 // Get email at invalid index
240 email = inbox.GetEmailAtIndex(999)
241 if email != nil {
242 t.Error("Expected nil for invalid index, got non-nil")
243 }
244
245 // Get email at negative index
246 email = inbox.GetEmailAtIndex(-1)
247 if email != nil {
248 t.Error("Expected nil for negative index, got non-nil")
249 }
250}