.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 ./...
drew created
.github/workflows/ci.yml | 3
view/html.go | 13 ++-
view/html_test.go | 139 ++++++++++++++++++++++++++++++++++++++++++
3 files changed, 148 insertions(+), 7 deletions(-)
@@ -20,8 +20,5 @@ jobs:
- name: Tidy modules
run: go mod tidy
- - name: Build
- run: go build -v ./...
-
- name: Test
run: go test -v ./...
@@ -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
-}
+}
@@ -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: "<h1>Hello</h1>",
+ },
+ {
+ name: "Bold",
+ input: "**bold text**",
+ expected: "<p><strong>bold text</strong></p>",
+ },
+ {
+ name: "Link",
+ input: "[link](http://example.com)",
+ expected: `<p><a href="http://example.com">link</a></p>`,
+ },
+ }
+
+ 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: "<p>Hello, world!</p>",
+ expected: "Hello, world!",
+ },
+ {
+ name: "With link HTML",
+ input: `<a href="http://example.com">Click here</a>`,
+ expected: "Click here",
+ },
+ {
+ name: "With image HTML",
+ input: `<img src="http://example.com/img.png" alt="alt text">`,
+ expected: "[Click here to view image: alt text]",
+ },
+ {
+ name: "With headers HTML",
+ input: "<h1>Header 1</h1>",
+ expected: "Header 1",
+ },
+ {
+ name: "With link Markdown",
+ input: `[Click here](http://example.com)`,
+ expected: "Click here",
+ },
+ {
+ name: "With image Markdown",
+ input: `>`,
+ 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)
+ }
+ })
+ }
+}