diff --git a/internal/tui/mail.go b/internal/tui/mail.go index 2a634ade..aa3cfd8c 100644 --- a/internal/tui/mail.go +++ b/internal/tui/mail.go @@ -2981,16 +2981,23 @@ func (v *mailView) renderEntries(entries []mail.Entry) (string, []int) { from = e.AlternativeSenderName } - // A full blank line separates the header from whatever follows it — - // the summary here, or the body's own leading blank when there is none. + // Each arm below opens with a blank line, which is what separates the header + // from whatever follows it. The summary is HEY's ~105-character preview of the + // body, so it stands in only where HEY served no body at all — the same ladder + // as printThreadStyled in internal/cmd/topic.go. Printed beside a body it repeats + // the message's opening line; printed for a body that was read and rendered to + // nothing, or for one that was not read, it passes a preview off as the message. fmt.Fprintf(&b, "%s %s\n", v.vc.styles.entryFrom.Render(terminal.SanitizeLine(from)), v.vc.styles.entryDate.Render(formatDisplayDateTime(e.CreatedAt))) - if e.Summary != "" { - fmt.Fprintf(&b, "\n%s\n", terminal.SanitizeLine(e.Summary)) - } switch { case !e.Body.IsEmpty(): fmt.Fprintf(&b, "\n%s\n", v.vc.styles.entryBody.Render(markdown.Render(e.Body, sepWidth))) - case e.BodyState == string(threadload.StateOverLimit), e.BodyState == string(threadload.StateFailed): + case e.BodyState == string(threadload.StateHydrated): + fmt.Fprintf(&b, "\n%s\n", v.vc.styles.entryDate.Render("(empty body)")) + case e.BodyState == string(threadload.StateBodyless) && e.Summary != "": + fmt.Fprintf(&b, "\n%s\n", terminal.SanitizeLine(e.Summary)) + case e.BodyState == string(threadload.StateBodyless): + fmt.Fprintf(&b, "\n%s\n", v.vc.styles.entryDate.Render("(no body)")) + default: fmt.Fprintf(&b, "\n%s\n", v.vc.styles.entryDate.Render("(body not read: "+e.BodyState+")")) } entryAttachments := attachmentsForMessage(v.attachments, e.ID) diff --git a/internal/tui/mail_test.go b/internal/tui/mail_test.go index 8da197f8..a1bfe59e 100644 --- a/internal/tui/mail_test.go +++ b/internal/tui/mail_test.go @@ -26,6 +26,7 @@ import ( "github.com/basecamp/hey-cli/internal/apierr" "github.com/basecamp/hey-cli/internal/htmlutil" "github.com/basecamp/hey-cli/internal/mail" + "github.com/basecamp/hey-cli/internal/threadload" ) func testPNG(t *testing.T) []byte { @@ -331,8 +332,8 @@ func TestThreadViewShowsSubjectAndSpacesTheHeader(t *testing.T) { if strings.TrimSpace(lines[header+1]) != "" { t.Errorf("a blank line should follow the header: %q", lines[header+1]) } - if strings.TrimSpace(lines[header+2]) != "Can we meet Thursday?" { - t.Errorf("the summary should follow the blank line: %q", lines[header+2]) + if strings.TrimSpace(lines[header+2]) != "Can we meet Thursday to walk through the numbers?" { + t.Errorf("the body should follow the blank line: %q", lines[header+2]) } } @@ -3710,6 +3711,60 @@ func TestMailViewReadsEveryPageOfAThreadAndMarksUnreadBodies(t *testing.T) { } } +// HEY's summary is its ~105-character preview of the body, so it belongs on screen only +// where the body itself cannot be. Beside a body it repeats the message's opening line; +// for a body that was not read it would pass a preview off as the message. +func TestRenderEntriesShowsTheBodyRatherThanHEYsPreviewOfIt(t *testing.T) { + v := newMailView(testVC()) + v.vc.width = 80 + entries := []mail.Entry{ + { + ID: 1, + Creator: mail.Contact{Name: "Maria Gonzalez"}, + Summary: "Moving the review to Friday so ...", + Body: htmlutil.ToMarkdown("

Moving the review to Friday so Sam can join.

"), + BodyState: string(threadload.StateHydrated), + }, + { + ID: 2, + Creator: mail.Contact{Name: "Sam Rivera"}, + Summary: "The quarterly figures are attached.", + BodyState: string(threadload.StateBodyless), + }, + { + ID: 3, + Creator: mail.Contact{Name: "Ana Ortiz"}, + Summary: "Never shown, the body went unread ...", + BodyState: string(threadload.StateFailed), + }, + { + ID: 4, + Creator: mail.Contact{Name: "Lee Park"}, + Summary: "Markup only, so HEY still has a preview ...", + Body: htmlutil.ToMarkdown("

"), + BodyState: string(threadload.StateHydrated), + }, + } + + rendered, _ := v.renderEntries(entries) + + if strings.Contains(rendered, "Friday so ...") { + t.Errorf("a hydrated entry repeats HEY's preview of its own body:\n%s", rendered) + } + if !strings.Contains(rendered, "Sam can join.") { + t.Errorf("a hydrated entry lost its body:\n%s", rendered) + } + if !strings.Contains(rendered, "The quarterly figures are attached.") { + t.Errorf("a bodyless entry lost its summary, which is all HEY serves for it:\n%s", rendered) + } + if strings.Contains(rendered, "Never shown") || !strings.Contains(rendered, "(body not read: failed)") { + t.Errorf("an unread body should say so rather than show a preview:\n%s", rendered) + } + if strings.Contains(rendered, "Markup only") || !strings.Contains(rendered, "(empty body)") { + t.Errorf("a body that was read and rendered to nothing should say so rather than show a preview:\n%s", rendered) + } +} + func TestRenderEntriesReportsEveryMessageHeaderOffset(t *testing.T) { v := newMailView(testVC()) v.vc.width = 60