feat: more tests added

drew created

Change summary

fetcher/fetcher_test.go | 49 ++++++++++++++++++++++++++
tui/composer_test.go    | 81 +++++++++++++++++++++++++++++++++++++++++++
tui/inbox_test.go       | 46 ++++++++++++++++++++++++
3 files changed, 176 insertions(+)

Detailed changes

fetcher/fetcher_test.go 🔗

@@ -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)
+		}
+	}
+}

tui/composer_test.go 🔗

@@ -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)
+		}
+	})
+}

tui/inbox_test.go 🔗

@@ -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)
+		}
+	})
+}