composer_test.go

  1package tui
  2
  3import (
  4	"testing"
  5
  6	tea "github.com/charmbracelet/bubbletea"
  7	"github.com/floatpane/matcha/config"
  8)
  9
 10// TestComposerUpdate verifies the state transitions in the email composer.
 11func TestComposerUpdate(t *testing.T) {
 12	// Initialize a new composer with accounts.
 13	accounts := []config.Account{
 14		{ID: "account-1", Email: "test@example.com", Name: "Test User"},
 15	}
 16	composer := NewComposerWithAccounts(accounts, "account-1", "", "", "")
 17
 18	t.Run("Focus cycling", func(t *testing.T) {
 19		// Initial focus is on the 'To' input (index 1, since From is 0).
 20		// But NewComposer starts focus at focusTo which is 1.
 21		if composer.focusIndex != focusTo {
 22			t.Errorf("Initial focusIndex should be %d (focusTo), got %d", focusTo, composer.focusIndex)
 23		}
 24
 25		// Simulate pressing Tab to move to the 'Subject' field.
 26		model, _ := composer.Update(tea.KeyMsg{Type: tea.KeyTab})
 27		composer = model.(*Composer)
 28		if composer.focusIndex != focusSubject {
 29			t.Errorf("After one Tab, focusIndex should be %d (focusSubject), got %d", focusSubject, composer.focusIndex)
 30		}
 31
 32		// Simulate pressing Tab again to move to the 'Body' field.
 33		model, _ = composer.Update(tea.KeyMsg{Type: tea.KeyTab})
 34		composer = model.(*Composer)
 35		if composer.focusIndex != focusBody {
 36			t.Errorf("After two Tabs, focusIndex should be %d (focusBody), got %d", focusBody, composer.focusIndex)
 37		}
 38
 39		// Simulate pressing Tab again to move to the 'Attachment' field.
 40		model, _ = composer.Update(tea.KeyMsg{Type: tea.KeyTab})
 41		composer = model.(*Composer)
 42		if composer.focusIndex != focusAttachment {
 43			t.Errorf("After three Tabs, focusIndex should be %d (focusAttachment), got %d", focusAttachment, composer.focusIndex)
 44		}
 45
 46		// Simulate pressing Tab again to move to the 'Send' button.
 47		model, _ = composer.Update(tea.KeyMsg{Type: tea.KeyTab})
 48		composer = model.(*Composer)
 49		if composer.focusIndex != focusSend {
 50			t.Errorf("After four Tabs, focusIndex should be %d (focusSend), got %d", focusSend, composer.focusIndex)
 51		}
 52
 53		// Simulate one more Tab to wrap around to the 'From' field.
 54		model, _ = composer.Update(tea.KeyMsg{Type: tea.KeyTab})
 55		composer = model.(*Composer)
 56		if composer.focusIndex != focusFrom {
 57			t.Errorf("After five Tabs, focusIndex should wrap to %d (focusFrom), got %d", focusFrom, composer.focusIndex)
 58		}
 59	})
 60
 61	t.Run("Send email message", func(t *testing.T) {
 62		// Re-initialize composer for this test
 63		composer = NewComposerWithAccounts(accounts, "account-1", "", "", "")
 64
 65		// Set values for the email fields.
 66		composer.toInput.SetValue("recipient@example.com")
 67		composer.subjectInput.SetValue("Test Subject")
 68		composer.bodyInput.SetValue("This is the body.")
 69		// Set focus to the Send button.
 70		composer.focusIndex = focusSend
 71
 72		// Simulate pressing Enter to send the email.
 73		_, cmd := composer.Update(tea.KeyMsg{Type: tea.KeyEnter})
 74		if cmd == nil {
 75			t.Fatal("Expected a command to be returned, but got nil.")
 76		}
 77
 78		// Execute the command and check the resulting message.
 79		msg := cmd()
 80		sendMsg, ok := msg.(SendEmailMsg)
 81		if !ok {
 82			t.Fatalf("Expected a SendEmailMsg, but got %T", msg)
 83		}
 84
 85		// Verify the content of the message.
 86		if sendMsg.To != "recipient@example.com" {
 87			t.Errorf("Expected To 'recipient@example.com', got %q", sendMsg.To)
 88		}
 89		if sendMsg.Subject != "Test Subject" {
 90			t.Errorf("Expected Subject 'Test Subject', got %q", sendMsg.Subject)
 91		}
 92		if sendMsg.Body != "This is the body." {
 93			t.Errorf("Expected Body 'This is the body.', got %q", sendMsg.Body)
 94		}
 95		if sendMsg.AccountID != "account-1" {
 96			t.Errorf("Expected AccountID 'account-1', got %q", sendMsg.AccountID)
 97		}
 98	})
 99
100	t.Run("Account picker with multiple accounts", func(t *testing.T) {
101		multiAccounts := []config.Account{
102			{ID: "account-1", Email: "test1@example.com", Name: "User 1"},
103			{ID: "account-2", Email: "test2@example.com", Name: "User 2"},
104		}
105		multiComposer := NewComposerWithAccounts(multiAccounts, "account-1", "", "", "")
106
107		// Move focus to From field
108		multiComposer.focusIndex = focusFrom
109
110		// Press Enter to open account picker
111		model, _ := multiComposer.Update(tea.KeyMsg{Type: tea.KeyEnter})
112		multiComposer = model.(*Composer)
113
114		if !multiComposer.showAccountPicker {
115			t.Error("Expected account picker to be shown")
116		}
117
118		// Navigate down to select second account
119		model, _ = multiComposer.Update(tea.KeyMsg{Type: tea.KeyDown})
120		multiComposer = model.(*Composer)
121
122		if multiComposer.selectedAccountIdx != 1 {
123			t.Errorf("Expected selectedAccountIdx to be 1, got %d", multiComposer.selectedAccountIdx)
124		}
125
126		// Press Enter to confirm selection
127		model, _ = multiComposer.Update(tea.KeyMsg{Type: tea.KeyEnter})
128		multiComposer = model.(*Composer)
129
130		if multiComposer.showAccountPicker {
131			t.Error("Expected account picker to be closed")
132		}
133
134		// Verify the selected account
135		if multiComposer.GetSelectedAccountID() != "account-2" {
136			t.Errorf("Expected selected account ID 'account-2', got %q", multiComposer.GetSelectedAccountID())
137		}
138	})
139
140	t.Run("Single account no picker", func(t *testing.T) {
141		singleAccounts := []config.Account{
142			{ID: "account-1", Email: "test@example.com"},
143		}
144		singleComposer := NewComposerWithAccounts(singleAccounts, "account-1", "", "", "")
145
146		// Move focus to From field
147		singleComposer.focusIndex = focusFrom
148
149		// Press Enter - should not open picker with single account
150		model, _ := singleComposer.Update(tea.KeyMsg{Type: tea.KeyEnter})
151		singleComposer = model.(*Composer)
152
153		if singleComposer.showAccountPicker {
154			t.Error("Account picker should not open with single account")
155		}
156	})
157}
158
159// TestComposerGetFromAddress verifies the from address formatting.
160func TestComposerGetFromAddress(t *testing.T) {
161	t.Run("With name", func(t *testing.T) {
162		accounts := []config.Account{
163			{ID: "account-1", Email: "test@example.com", Name: "Test User"},
164		}
165		composer := NewComposerWithAccounts(accounts, "account-1", "", "", "")
166
167		fromAddr := composer.getFromAddress()
168		expected := "Test User <test@example.com>"
169		if fromAddr != expected {
170			t.Errorf("Expected from address %q, got %q", expected, fromAddr)
171		}
172	})
173
174	t.Run("Without name", func(t *testing.T) {
175		accounts := []config.Account{
176			{ID: "account-1", Email: "test@example.com"},
177		}
178		composer := NewComposerWithAccounts(accounts, "account-1", "", "", "")
179
180		fromAddr := composer.getFromAddress()
181		expected := "test@example.com"
182		if fromAddr != expected {
183			t.Errorf("Expected from address %q, got %q", expected, fromAddr)
184		}
185	})
186
187	t.Run("No accounts", func(t *testing.T) {
188		composer := NewComposer("", "", "", "")
189
190		fromAddr := composer.getFromAddress()
191		if fromAddr != "" {
192			t.Errorf("Expected empty from address, got %q", fromAddr)
193		}
194	})
195}
196
197// TestComposerSetSelectedAccount verifies account selection.
198func TestComposerSetSelectedAccount(t *testing.T) {
199	accounts := []config.Account{
200		{ID: "account-1", Email: "test1@example.com"},
201		{ID: "account-2", Email: "test2@example.com"},
202		{ID: "account-3", Email: "test3@example.com"},
203	}
204	composer := NewComposerWithAccounts(accounts, "account-1", "", "", "")
205
206	composer.SetSelectedAccount("account-3")
207	if composer.selectedAccountIdx != 2 {
208		t.Errorf("Expected selectedAccountIdx 2, got %d", composer.selectedAccountIdx)
209	}
210	if composer.GetSelectedAccountID() != "account-3" {
211		t.Errorf("Expected selected account ID 'account-3', got %q", composer.GetSelectedAccountID())
212	}
213
214	// Test non-existent account (should not change)
215	composer.SetSelectedAccount("non-existent")
216	if composer.selectedAccountIdx != 2 {
217		t.Errorf("Expected selectedAccountIdx to remain 2, got %d", composer.selectedAccountIdx)
218	}
219}