@@ -0,0 +1,49 @@
+package fetcher
+
+import (
+ "testing"
+
+ "github.com/andrinoff/email-cli/config"
+)
+
+// TestFetchEmails is an integration test that requires a live IMAP server and valid credentials.
+// NOTE: This test will be skipped if it cannot load a configuration file,
+// making it safe to run in a CI environment without credentials.
+// To run this test locally, ensure you have a valid `config.json` file.
+func TestFetchEmails(t *testing.T) {
+ // Attempt to load the configuration.
+ cfg, err := config.LoadConfig()
+ if err != nil {
+ // If config doesn't exist, skip the test. This is useful for CI environments.
+ t.Skipf("Skipping TestFetchEmails: could not load config: %v", err)
+ }
+
+ // If the password is a placeholder, skip the test to avoid failed auth attempts.
+ if cfg.Password == "" || cfg.Password == "supersecret" {
+ t.Skip("Skipping TestFetchEmails: placeholder or empty password found in config.")
+ }
+
+ emails, err := FetchEmails(cfg)
+ if err != nil {
+ t.Fatalf("FetchEmails() failed with error: %v", err)
+ }
+
+ if len(emails) == 0 {
+ // This is not necessarily a failure, but we can log it.
+ t.Log("FetchEmails() returned 0 emails. This might be expected.")
+ }
+
+ // Check that the emails are sorted from newest to oldest.
+ if len(emails) > 1 {
+ if emails[0].Date.Before(emails[len(emails)-1].Date) {
+ t.Error("Emails do not appear to be sorted from newest to oldest.")
+ }
+ }
+
+ // Check a sample email for expected content.
+ for _, email := range emails {
+ if email.Subject == "" && email.From == "" {
+ t.Errorf("Fetched email has empty subject and from fields: %+v", email)
+ }
+ }
+}
@@ -0,0 +1,81 @@
+package tui
+
+import (
+ "reflect"
+ "testing"
+
+ tea "github.com/charmbracelet/bubbletea"
+)
+
+// TestComposerUpdate verifies the state transitions in the email composer.
+func TestComposerUpdate(t *testing.T) {
+ // Initialize a new composer.
+ composer := NewComposer("test@example.com")
+
+ t.Run("Focus cycling", func(t *testing.T) {
+ // Initial focus is on the 'To' input (index 0).
+ if composer.focusIndex != 0 {
+ t.Errorf("Initial focusIndex should be 0, got %d", composer.focusIndex)
+ }
+
+ // Simulate pressing Tab to move to the 'Subject' field.
+ model, _ := composer.Update(tea.KeyMsg{Type: tea.KeyTab})
+ composer = model.(Composer)
+ if composer.focusIndex != 1 {
+ t.Errorf("After one Tab, focusIndex should be 1, got %d", composer.focusIndex)
+ }
+
+ // Simulate pressing Tab again to move to the 'Body' field.
+ model, _ = composer.Update(tea.KeyMsg{Type: tea.KeyTab})
+ composer = model.(Composer)
+ if composer.focusIndex != 2 {
+ t.Errorf("After two Tabs, focusIndex should be 2, got %d", composer.focusIndex)
+ }
+
+ // Simulate pressing Tab again to move to the 'Send' button.
+ model, _ = composer.Update(tea.KeyMsg{Type: tea.KeyTab})
+ composer = model.(Composer)
+ if composer.focusIndex != 3 {
+ t.Errorf("After three Tabs, focusIndex should be 3 (Send), got %d", composer.focusIndex)
+ }
+
+ // Simulate one more Tab to wrap around to the 'To' field.
+ model, _ = composer.Update(tea.KeyMsg{Type: tea.KeyTab})
+ composer = model.(Composer)
+ if composer.focusIndex != 0 {
+ t.Errorf("After four Tabs, focusIndex should wrap to 0, got %d", composer.focusIndex)
+ }
+ })
+
+ t.Run("Send email message", func(t *testing.T) {
+ // Set values for the email fields.
+ composer.toInput.SetValue("recipient@example.com")
+ composer.subjectInput.SetValue("Test Subject")
+ composer.bodyInput.SetValue("This is the body.")
+ // Set focus to the Send button.
+ composer.focusIndex = 3
+
+ // Simulate pressing Enter to send the email.
+ _, cmd := composer.Update(tea.KeyMsg{Type: tea.KeyEnter})
+ if cmd == nil {
+ t.Fatal("Expected a command to be returned, but got nil.")
+ }
+
+ // Execute the command and check the resulting message.
+ msg := cmd()
+ sendMsg, ok := msg.(SendEmailMsg)
+ if !ok {
+ t.Fatalf("Expected a SendEmailMsg, but got %T", msg)
+ }
+
+ // Verify the content of the message.
+ expectedMsg := SendEmailMsg{
+ To: "recipient@example.com",
+ Subject: "Test Subject",
+ Body: "This is the body.",
+ }
+ if !reflect.DeepEqual(sendMsg, expectedMsg) {
+ t.Errorf("Mismatched SendEmailMsg.\nGot: %+v\nWant: %+v", sendMsg, expectedMsg)
+ }
+ })
+}
@@ -0,0 +1,46 @@
+package tui
+
+import (
+ "testing"
+ "time"
+
+ "github.com/andrinoff/email-cli/fetcher"
+ tea "github.com/charmbracelet/bubbletea"
+)
+
+// TestInboxUpdate verifies the state transitions in the inbox view.
+func TestInboxUpdate(t *testing.T) {
+ // Create a sample list of emails.
+ sampleEmails := []fetcher.Email{
+ {From: "a@example.com", Subject: "Email 1", Date: time.Now()},
+ {From: "b@example.com", Subject: "Email 2", Date: time.Now().Add(-time.Hour)},
+ {From: "c@example.com", Subject: "Email 3", Date: time.Now().Add(-2 * time.Hour)},
+ }
+
+ inbox := NewInbox(sampleEmails)
+
+ t.Run("Select email to view", func(t *testing.T) {
+ // By default, the first item is selected (index 0).
+ // Move down to the second item (index 1).
+ inbox.list, _ = inbox.list.Update(tea.KeyMsg{Type: tea.KeyDown})
+
+ // Simulate pressing Enter to view the selected email.
+ _, cmd := inbox.Update(tea.KeyMsg{Type: tea.KeyEnter})
+ if cmd == nil {
+ t.Fatal("Expected a command, but got nil.")
+ }
+
+ // Check the resulting message.
+ msg := cmd()
+ viewMsg, ok := msg.(ViewEmailMsg)
+ if !ok {
+ t.Fatalf("Expected a ViewEmailMsg, but got %T", msg)
+ }
+
+ // The index should match the selected item in the list.
+ expectedIndex := 1
+ if viewMsg.Index != expectedIndex {
+ t.Errorf("Expected index %d, but got %d", expectedIndex, viewMsg.Index)
+ }
+ })
+}