A minimal email TUI where you read with Markdown and write in Neovim.
neomd.ssp.sh/docs
email
markdown
neovim
tui
1package render
2
3import (
4 "strings"
5 "testing"
6)
7
8func TestToHTML_Bold(t *testing.T) {
9 out, err := ToHTML("**bold** text")
10 if err != nil {
11 t.Fatalf("ToHTML returned error: %v", err)
12 }
13 if !strings.Contains(out, "<strong>bold</strong>") {
14 t.Errorf("expected <strong>bold</strong> in output, got:\n%s", out)
15 }
16}
17
18func TestToHTML_HTMLWrapper(t *testing.T) {
19 out, err := ToHTML("hello")
20 if err != nil {
21 t.Fatalf("ToHTML returned error: %v", err)
22 }
23 if !strings.HasPrefix(out, "<!DOCTYPE html>") {
24 t.Errorf("expected output to start with <!DOCTYPE html>, got:\n%.80s...", out)
25 }
26 if !strings.Contains(out, "<body>") {
27 t.Errorf("expected <body> in output")
28 }
29}
30
31func TestToHTML_GFMTable(t *testing.T) {
32 md := "| A | B |\n|---|---|\n| 1 | 2 |\n"
33 out, err := ToHTML(md)
34 if err != nil {
35 t.Fatalf("ToHTML returned error: %v", err)
36 }
37 if !strings.Contains(out, "<table>") {
38 t.Errorf("expected <table> in output, got:\n%s", out)
39 }
40}
41
42func TestToHTML_CodeBlock(t *testing.T) {
43 md := "```go\nfmt.Println(\"hi\")\n```\n"
44 out, err := ToHTML(md)
45 if err != nil {
46 t.Fatalf("ToHTML returned error: %v", err)
47 }
48 if !strings.Contains(out, "<pre>") {
49 t.Errorf("expected <pre> in output, got:\n%s", out)
50 }
51}
52
53func TestToHTML_Empty(t *testing.T) {
54 out, err := ToHTML("")
55 if err != nil {
56 t.Fatalf("ToHTML returned error for empty input: %v", err)
57 }
58 if !strings.HasPrefix(out, "<!DOCTYPE html>") {
59 t.Errorf("expected DOCTYPE even for empty input, got:\n%.80s...", out)
60 }
61}
62
63func TestToANSI_Smoke(t *testing.T) {
64 _, err := ToANSI("# Hello\n\nSome **bold** text.", "dark", 80)
65 if err != nil {
66 t.Fatalf("ToANSI returned error: %v", err)
67 }
68}
69
70func TestToHTML_Callout_Note(t *testing.T) {
71 md := "> [!note]\n> This is a note callout\n"
72 out, err := ToHTML(md)
73 if err != nil {
74 t.Fatalf("ToHTML returned error: %v", err)
75 }
76 // Print actual HTML for debugging
77 t.Logf("Actual HTML output:\n%s", out)
78 if !strings.Contains(out, "callout") {
79 t.Errorf("expected 'callout' class in output, got:\n%s", out)
80 }
81 if !strings.Contains(out, "callout-note") {
82 t.Errorf("expected 'callout-note' class in output, got:\n%s", out)
83 }
84 if !strings.Contains(out, "This is a note callout") {
85 t.Errorf("expected callout content in output, got:\n%s", out)
86 }
87}
88
89func TestToHTML_Callout_WithTitle(t *testing.T) {
90 md := "> [!warning] Custom Warning Title\n> This is a warning\n"
91 out, err := ToHTML(md)
92 if err != nil {
93 t.Fatalf("ToHTML returned error: %v", err)
94 }
95 if !strings.Contains(out, "callout-warning") {
96 t.Errorf("expected 'callout-warning' class in output, got:\n%s", out)
97 }
98 if !strings.Contains(out, "Custom Warning Title") {
99 t.Errorf("expected custom title in output, got:\n%s", out)
100 }
101 if !strings.Contains(out, "This is a warning") {
102 t.Errorf("expected callout content in output, got:\n%s", out)
103 }
104}
105
106func TestToHTML_Callout_MultiParagraph(t *testing.T) {
107 md := "> [!tip]\n> First paragraph\n> \n> Second paragraph\n"
108 out, err := ToHTML(md)
109 if err != nil {
110 t.Fatalf("ToHTML returned error: %v", err)
111 }
112 if !strings.Contains(out, "callout-tip") {
113 t.Errorf("expected 'callout-tip' class in output, got:\n%s", out)
114 }
115 if !strings.Contains(out, "First paragraph") {
116 t.Errorf("expected first paragraph in output, got:\n%s", out)
117 }
118 if !strings.Contains(out, "Second paragraph") {
119 t.Errorf("expected second paragraph in output, got:\n%s", out)
120 }
121}
122
123func TestToHTML_Callout_Types(t *testing.T) {
124 tests := []struct {
125 name string
126 callType string
127 wantClass string
128 }{
129 {"note", "note", "callout-note"},
130 {"tip", "tip", "callout-tip"},
131 {"important", "important", "callout-important"},
132 {"warning", "warning", "callout-warning"},
133 {"caution", "caution", "callout-caution"},
134 }
135
136 for _, tt := range tests {
137 t.Run(tt.name, func(t *testing.T) {
138 md := "> [!" + tt.callType + "]\n> Test content\n"
139 out, err := ToHTML(md)
140 if err != nil {
141 t.Fatalf("ToHTML returned error: %v", err)
142 }
143 if !strings.Contains(out, tt.wantClass) {
144 t.Errorf("expected '%s' class in output, got:\n%s", tt.wantClass, out)
145 }
146 })
147 }
148}
149
150func TestToHTML_Callout_NoSpaceSyntax(t *testing.T) {
151 // Test if >[!note] works without space after >
152 md := ">[!note] No Space Test\n>This tests the syntax without space\n"
153 out, err := ToHTML(md)
154 if err != nil {
155 t.Fatalf("ToHTML returned error: %v", err)
156 }
157 // Check if it rendered as callout or as regular blockquote
158 if strings.Contains(out, "callout-note") {
159 // Success: >[!note] (no space) DOES work as callout
160 if !strings.Contains(out, "No Space Test") {
161 t.Errorf("expected title in callout output, got:\n%s", out)
162 }
163 if !strings.Contains(out, "This tests the syntax without space") {
164 t.Errorf("expected content in callout output, got:\n%s", out)
165 }
166 } else {
167 // Failure: rendered as blockquote instead
168 t.Errorf(">[!note] without space did not render as callout. Use '> [!note]' (with space) instead. Got:\n%s", out)
169 }
170}
171
172func TestFormatCalloutsForPlainText_WithTitle(t *testing.T) {
173 input := "> [!tip] Good News\n> We're ahead of schedule!\n"
174 expected := "💡 Good News\nWe're ahead of schedule!\n"
175 got := FormatCalloutsForPlainText(input)
176 if got != expected {
177 t.Errorf("FormatCalloutsForPlainText with title:\nwant: %q\ngot: %q", expected, got)
178 }
179}
180
181func TestFormatCalloutsForPlainText_NoTitle(t *testing.T) {
182 input := "> [!note]\n> This is a note\n"
183 expected := "📘 Note\nThis is a note\n"
184 got := FormatCalloutsForPlainText(input)
185 if got != expected {
186 t.Errorf("FormatCalloutsForPlainText without title:\nwant: %q\ngot: %q", expected, got)
187 }
188}
189
190func TestFormatCalloutsForPlainText_MultipleCallouts(t *testing.T) {
191 input := "> [!warning] Action Required\n> Please review by Friday.\n\n> [!note]\n> Please read\n"
192 expected := "⚠️ Action Required\nPlease review by Friday.\n\n📘 Note\nPlease read\n"
193 got := FormatCalloutsForPlainText(input)
194 if got != expected {
195 t.Errorf("FormatCalloutsForPlainText with multiple callouts:\nwant: %q\ngot: %q", expected, got)
196 }
197}
198
199func TestFormatCalloutsForPlainText_NoSpaceAfterArrow(t *testing.T) {
200 input := ">[!tip] Title\n>Content here\n"
201 got := FormatCalloutsForPlainText(input)
202 if !strings.Contains(got, "💡 Title") {
203 t.Errorf("FormatCalloutsForPlainText should handle >[!type] without space:\ngot: %q", got)
204 }
205 if !strings.Contains(got, "Content here") {
206 t.Errorf("FormatCalloutsForPlainText should unquote content:\ngot: %q", got)
207 }
208 // Should NOT contain > markers
209 if strings.Contains(got, ">") {
210 t.Errorf("FormatCalloutsForPlainText should remove blockquote markers:\ngot: %q", got)
211 }
212}
213
214func TestFormatCalloutsForPlainText_AllTypes(t *testing.T) {
215 tests := []struct {
216 callType string
217 wantIcon string
218 }{
219 {"note", "📘"},
220 {"tip", "💡"},
221 {"warning", "⚠️"},
222 {"danger", "🚨"},
223 {"success", "✅"},
224 {"info", "ℹ️"},
225 {"question", "❓"},
226 {"bug", "🐛"},
227 {"example", "📝"},
228 }
229
230 for _, tt := range tests {
231 t.Run(tt.callType, func(t *testing.T) {
232 input := "> [!" + tt.callType + "] Title\n> Content\n"
233 got := FormatCalloutsForPlainText(input)
234 if !strings.Contains(got, tt.wantIcon) {
235 t.Errorf("expected icon %s for type %s, got: %q", tt.wantIcon, tt.callType, got)
236 }
237 })
238 }
239}
240
241func TestFormatCalloutsForPlainText_PreservesNonCallouts(t *testing.T) {
242 input := "Regular text\n\n> Regular blockquote\n> without callout\n\n> [!note] Callout\n> With content\n"
243 got := FormatCalloutsForPlainText(input)
244
245 // Should preserve regular text and non-callout blockquotes
246 if !strings.Contains(got, "Regular text") {
247 t.Error("should preserve regular text")
248 }
249 if !strings.Contains(got, "> Regular blockquote") {
250 t.Error("should preserve regular blockquotes")
251 }
252 // Should format the callout (no blockquote marker)
253 if !strings.Contains(got, "📘 Callout") {
254 t.Error("should format callout syntax")
255 }
256 if !strings.Contains(got, "With content") {
257 t.Error("should include callout content")
258 }
259}
260
261func TestFormatCalloutsForPlainText_MultiParagraphCallout(t *testing.T) {
262 input := "> [!tip] Title\n> First paragraph\n> \n> Second paragraph\n"
263 got := FormatCalloutsForPlainText(input)
264
265 if !strings.Contains(got, "💡 Title") {
266 t.Errorf("should have emoji title, got: %q", got)
267 }
268 if !strings.Contains(got, "First paragraph") {
269 t.Errorf("should have first paragraph, got: %q", got)
270 }
271 if !strings.Contains(got, "Second paragraph") {
272 t.Errorf("should have second paragraph, got: %q", got)
273 }
274 // Should not have > markers
275 lines := strings.Split(got, "\n")
276 for _, line := range lines {
277 if strings.TrimSpace(line) != "" && strings.HasPrefix(strings.TrimSpace(line), ">") {
278 t.Errorf("should not have > markers in callout content, got line: %q", line)
279 }
280 }
281}