cli + tui to publish to leaflet (wip) & manage tasks, notes & watch/read lists 馃崈
charm
leaflet
readability
golang
1package handlers
2
3import (
4 "errors"
5 "strings"
6 "testing"
7)
8
9type renderMarkdownTC struct {
10 name string
11 content string
12 err bool
13 contains []string
14}
15
16type fakeRenderer struct {
17 fail bool
18}
19
20func (f fakeRenderer) Render(s string) (string, error) {
21 if f.fail {
22 return "", errors.New("render error")
23 }
24 return "fake:" + s, nil
25}
26
27var defaultRenderer = newRenderer
28
29func TestRenderMarkdown(t *testing.T) {
30 tt := []renderMarkdownTC{
31 {name: "simple text", content: "Hello, world!", err: false, contains: []string{"Hello, world!"}},
32 {name: "markdown heading", content: "# Main Title", err: false, contains: []string{"Main Title"}},
33 {name: "markdown with emphasis", content: "This is **bold** and *italic* text", err: false, contains: []string{"bold", "italic"}},
34 {name: "markdown list", content: "- Item 1\n- Item 2\n- Item 3", err: false, contains: []string{"Item 1", "Item 2", "Item 3"}},
35 {name: "code block", content: "```go\nfunc main() {\n fmt.Println(\"Hello\")\n}\n```", err: false, contains: []string{"main", "fmt.Println"}},
36 {name: "empty string", content: "", err: false, contains: []string{}},
37 {name: "only whitespace", content: " \n\t \n ", err: false, contains: []string{}},
38 {name: "mixed content", content: "# Title\n\nSome **bold** text and a [link](https://example.com)\n\n- List item",
39 err: false, contains: []string{"Title", "bold", "example.com", "List item"}},
40 }
41
42 for _, tc := range tt {
43 t.Run(tc.name, func(t *testing.T) {
44 defer func() { newRenderer = defaultRenderer }()
45
46 result, err := renderMarkdown(tc.content)
47 if tc.err && err == nil {
48 t.Fatalf("expected error, got nil")
49 }
50 if !tc.err && err != nil {
51 t.Fatalf("unexpected error: %v", err)
52 }
53
54 for _, want := range tc.contains {
55 if !strings.Contains(result, want) {
56 t.Fatalf("result should contain %q, got:\n%s", want, result)
57 }
58 }
59 })
60 }
61
62 t.Run("WordWrap", func(t *testing.T) {
63 defer func() { newRenderer = defaultRenderer }()
64 text := strings.Repeat("This is a very long line that should be wrapped at 80 characters. ", 5)
65 result, err := renderMarkdown(text)
66 if err != nil {
67 t.Fatalf("unexpected error: %v", err)
68 }
69
70 lines := strings.Split(result, "\n")
71 for i, line := range lines {
72 cleaned := removeANSI(line)
73 if len(cleaned) > 85 {
74 t.Fatalf("Line at index %d is too long (%d chars): %q", i, len(cleaned), cleaned)
75 }
76 }
77 })
78
79 t.Run("RendererCreationFails", func(t *testing.T) {
80 newRenderer = func() (MarkdownRenderer, error) {
81 return nil, errors.New("forced renderer creation error")
82 }
83 _, err := renderMarkdown("test")
84 if err == nil || !strings.Contains(err.Error(), "failed to create markdown renderer") {
85 t.Fatalf("expected creation error, got %v", err)
86 }
87 })
88
89 t.Run("RenderFails", func(t *testing.T) {
90 newRenderer = func() (MarkdownRenderer, error) {
91 return fakeRenderer{fail: true}, nil
92 }
93 _, err := renderMarkdown("test")
94 if err == nil || !strings.Contains(err.Error(), "failed to render markdown") {
95 t.Fatalf("expected render error, got %v", err)
96 }
97 })
98}
99
100func removeANSI(s string) string {
101 result := ""
102 inEscapeCh := false
103 for i := 0; i < len(s); i++ {
104 if s[i] == '\x1b' && i+1 < len(s) && s[i+1] == '[' {
105 inEscapeCh = true
106 i++
107 continue
108 }
109 if inEscapeCh {
110 if (s[i] >= 'A' && s[i] <= 'Z') || (s[i] >= 'a' && s[i] <= 'z') {
111 inEscapeCh = false
112 }
113 continue
114 }
115 result += string(s[i])
116 }
117 return result
118}