folder_inbox_test.go

 1package tui
 2
 3import (
 4	"testing"
 5
 6	tea "charm.land/bubbletea/v2"
 7	"github.com/floatpane/matcha/config"
 8	"github.com/floatpane/matcha/fetcher"
 9)
10
11// TestFolderInboxSplitPreviewRendersSearchHit covers the case Lea reported on
12// PR #1186: opening a search result in split-pane mode used to silently drop
13// the keypress because the email was not in m.inbox.allEmails. After the fix
14// OpenSplitPreview accepts the resolved email and findEmailByUID falls back
15// to it, so PreviewBodyFetchedMsg can build the preview pane.
16func TestFolderInboxSplitPreviewRendersSearchHit(t *testing.T) {
17	accounts := []config.Account{
18		{ID: "account-1", Email: "host.example.com", FetchEmail: "first@example.com"},
19	}
20	fi := NewFolderInbox([]string{"INBOX", "Archive"}, accounts)
21	// Force a non-zero canvas so calculate*Width does not panic on Update.
22	model, _ := fi.Update(tea.WindowSizeMsg{Width: 200, Height: 60})
23	fi = model.(*FolderInbox)
24
25	// Search hit lives in a different folder; allEmails is empty.
26	hit := &fetcher.Email{
27		UID:       4242,
28		AccountID: "account-1",
29		MessageID: "<search-hit@example.com>",
30		From:      "sender@example.com",
31		To:        []string{"first@example.com"},
32		Subject:   "Search hit",
33	}
34
35	fi.OpenSplitPreview(hit.UID, hit.AccountID, hit)
36
37	if fi.previewSearchEmail == nil {
38		t.Fatal("OpenSplitPreview should retain the search hit email")
39	}
40	if got := fi.findEmailByUID(hit.UID, hit.AccountID); got == nil {
41		t.Fatal("findEmailByUID should fall back to the search hit email")
42	}
43
44	// Simulate the body arriving and verify the preview pane is built.
45	model, _ = fi.Update(PreviewBodyFetchedMsg{
46		UID:       hit.UID,
47		AccountID: hit.AccountID,
48		Body:      "hello body",
49	})
50	fi = model.(*FolderInbox)
51
52	if fi.previewPane == nil {
53		t.Fatal("expected previewPane to be built from the search hit fallback")
54	}
55
56	// closeSplitPreview must clear the cached search hit so a later open with
57	// no email cannot accidentally reuse the stale reference.
58	fi.closeSplitPreview()
59	if fi.previewSearchEmail != nil {
60		t.Fatal("closeSplitPreview should clear previewSearchEmail")
61	}
62}
63
64// TestFolderInboxSplitPreviewPrefersAllEmails verifies that when the email is
65// already known in allEmails, findEmailByUID returns the live entry (so reads
66// like IsRead stay current) instead of the snapshot passed via OpenSplitPreview.
67func TestFolderInboxSplitPreviewPrefersAllEmails(t *testing.T) {
68	accounts := []config.Account{
69		{ID: "account-1", Email: "host.example.com", FetchEmail: "first@example.com"},
70	}
71	fi := NewFolderInbox([]string{"INBOX"}, accounts)
72	model, _ := fi.Update(tea.WindowSizeMsg{Width: 200, Height: 60})
73	fi = model.(*FolderInbox)
74
75	live := fetcher.Email{UID: 7, AccountID: "account-1", Subject: "live", IsRead: true}
76	fi.SetEmails([]fetcher.Email{live}, accounts)
77
78	stale := &fetcher.Email{UID: 7, AccountID: "account-1", Subject: "stale", IsRead: false}
79	fi.OpenSplitPreview(live.UID, live.AccountID, stale)
80
81	got := fi.findEmailByUID(live.UID, live.AccountID)
82	if got == nil {
83		t.Fatal("findEmailByUID should resolve the email")
84	}
85	if got.Subject != "live" || !got.IsRead {
86		t.Fatalf("expected the live allEmails entry, got %+v", got)
87	}
88}