inbox_test.go

  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}