diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3b516bf78d703baa9a9a229b7df74d6cfbe2b0f9..305f87237596a6367d9cd6b35f324b0d61be449e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -20,8 +20,5 @@ jobs: - name: Tidy modules run: go mod tidy - - name: Build - run: go build -v ./... - - name: Test run: go test -v ./... diff --git a/view/html.go b/view/html.go index 1cdf4afb572916be422f6132d5959d5a8f67625f..1ef610880ed0c5ba7f24b8cae3f930182e1b544f 100644 --- a/view/html.go +++ b/view/html.go @@ -62,7 +62,8 @@ func ProcessBody(rawBody string, h1Style, h2Style, bodyStyle lipgloss.Style) (st doc.Find("style, script").Remove() - // Style headers + // Style headers by setting their text content. + // We use SetText so the h1/h2 tags remain in the document for spacing logic. doc.Find("h1").Each(func(i int, s *goquery.Selection) { s.SetText(h1Style.Render(s.Text())) }) @@ -71,8 +72,9 @@ func ProcessBody(rawBody string, h1Style, h2Style, bodyStyle lipgloss.Style) (st s.SetText(h2Style.Render(s.Text())) }) - // Add newlines after block elements for better spacing - doc.Find("p, div").Each(func(i int, s *goquery.Selection) { + // Add newlines after block elements for better spacing. + // THIS IS THE KEY FIX: Include h1 and h2 in the selector. + doc.Find("p, div, h1, h2").Each(func(i int, s *goquery.Selection) { s.After("\n\n") }) @@ -106,5 +108,8 @@ func ProcessBody(rawBody string, h1Style, h2Style, bodyStyle lipgloss.Style) (st re := regexp.MustCompile(`\n{3,}`) text = re.ReplaceAllString(text, "\n\n") + + text = strings.TrimSpace(text) + return bodyStyle.Render(text), nil -} +} \ No newline at end of file diff --git a/view/html_test.go b/view/html_test.go new file mode 100644 index 0000000000000000000000000000000000000000..e785914f0fd6bf26ee291fc8f90c5a998f3f4b05 --- /dev/null +++ b/view/html_test.go @@ -0,0 +1,139 @@ +package view + +import ( + "strings" + "testing" + + "github.com/charmbracelet/lipgloss" +) + +func TestDecodeQuotedPrintable(t *testing.T) { + testCases := []struct { + name string + input string + expected string + }{ + { + name: "Simple case", + input: "Hello=2C world=21", + expected: "Hello, world!", + }, + { + name: "With soft line break", + input: "This is a long line that gets wrapped=\r\n and continues here.", + expected: "This is a long line that gets wrapped and continues here.", + }, + { + name: "No encoding", + input: "Just a plain string.", + expected: "Just a plain string.", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + decoded, err := decodeQuotedPrintable(tc.input) + if err != nil { + t.Fatalf("decodeQuotedPrintable() failed: %v", err) + } + if decoded != tc.expected { + t.Errorf("Expected %q, got %q", tc.expected, decoded) + } + }) + } +} + +func TestMarkdownToHTML(t *testing.T) { + testCases := []struct { + name string + input string + expected string + }{ + { + name: "Heading", + input: "# Hello", + expected: "

Hello

", + }, + { + name: "Bold", + input: "**bold text**", + expected: "

bold text

", + }, + { + name: "Link", + input: "[link](http://example.com)", + expected: `

link

`, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + html := markdownToHTML([]byte(tc.input)) + // Trim newlines for consistent comparison + if strings.TrimSpace(string(html)) != tc.expected { + t.Errorf("Expected %s, got %s", tc.expected, html) + } + }) + } +} + +func TestProcessBody(t *testing.T) { + h1Style := lipgloss.NewStyle().SetString("H1") + h2Style := lipgloss.NewStyle().SetString("H2") + bodyStyle := lipgloss.NewStyle().SetString("BODY") + + testCases := []struct { + name string + input string + expected string + }{ + { + name: "Simple HTML", + input: "

Hello, world!

", + expected: "Hello, world!", + }, + { + name: "With link HTML", + input: `Click here`, + expected: "Click here", + }, + { + name: "With image HTML", + input: `alt text`, + expected: "[Click here to view image: alt text]", + }, + { + name: "With headers HTML", + input: "

Header 1

", + expected: "Header 1", + }, + { + name: "With link Markdown", + input: `[Click here](http://example.com)`, + expected: "Click here", + }, + { + name: "With image Markdown", + input: `![alt text](http://example.com/img.png)>`, + expected: "[Click here to view image: alt text]", + }, + { + name: "With headers Markdown", + input: "# Header 1", + expected: "Header 1", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + processed, err := ProcessBody(tc.input, h1Style, h2Style, bodyStyle) + if err != nil { + t.Fatalf("ProcessBody() failed: %v", err) + } + // Use Contains because styles add ANSI codes + if !strings.Contains(processed, tc.expected) { + t.Errorf("Processed body does not contain expected text.\nGot: %q\nWant to contain: %q", processed, tc.expected) + } + }) + } +} \ No newline at end of file