1package view
2
3import (
4 "os"
5 "regexp"
6 "strings"
7 "testing"
8
9 "charm.land/lipgloss/v2"
10)
11
12// clearAllTerminalEnv clears all environment variables that could indicate terminal capabilities
13func clearAllTerminalEnv() {
14 // Clear hyperlink support indicators
15 os.Unsetenv("VTE_VERSION")
16 os.Unsetenv("KITTY_WINDOW_ID")
17 os.Unsetenv("GHOSTTY_RESOURCES_DIR")
18 os.Unsetenv("WEZTERM_EXECUTABLE")
19 os.Unsetenv("WEZTERM_CONFIG_FILE")
20 os.Unsetenv("ITERM_SESSION_ID")
21 os.Unsetenv("ITERM_PROFILE")
22 os.Unsetenv("WARP_IS_LOCAL_SHELL_SESSION")
23 os.Unsetenv("WARP_COMBINED_PROMPT_COMMAND_FINISHED")
24 os.Unsetenv("KONSOLE_DBUS_SESSION")
25 os.Unsetenv("KONSOLE_VERSION")
26
27 // Set basic terminal that doesn't support anything special
28 os.Setenv("TERM", "xterm")
29 os.Setenv("TERM_PROGRAM", "basic")
30}
31
32func TestDecodeQuotedPrintable(t *testing.T) {
33 testCases := []struct {
34 name string
35 input string
36 expected string
37 }{
38 {
39 name: "Simple case",
40 input: "Hello=2C world=21",
41 expected: "Hello, world!",
42 },
43 {
44 name: "With soft line break",
45 input: "This is a long line that gets wrapped=\r\n and continues here.",
46 expected: "This is a long line that gets wrapped and continues here.",
47 },
48 {
49 name: "No encoding",
50 input: "Just a plain string.",
51 expected: "Just a plain string.",
52 },
53 }
54
55 for _, tc := range testCases {
56 t.Run(tc.name, func(t *testing.T) {
57 decoded, err := decodeQuotedPrintable(tc.input)
58 if err != nil {
59 t.Fatalf("decodeQuotedPrintable() failed: %v", err)
60 }
61 if decoded != tc.expected {
62 t.Errorf("Expected %q, got %q", tc.expected, decoded)
63 }
64 })
65 }
66}
67
68func TestMarkdownToHTML(t *testing.T) {
69 testCases := []struct {
70 name string
71 input string
72 expected string
73 }{
74 {
75 name: "Heading",
76 input: "# Hello",
77 expected: "<h1>Hello</h1>",
78 },
79 {
80 name: "Bold",
81 input: "**bold text**",
82 expected: "<p><strong>bold text</strong></p>",
83 },
84 {
85 name: "Link",
86 input: "[link](http://example.com)",
87 expected: `<p><a href="http://example.com">link</a></p>`,
88 },
89 }
90
91 for _, tc := range testCases {
92 t.Run(tc.name, func(t *testing.T) {
93 html := markdownToHTML([]byte(tc.input))
94 // Trim newlines for consistent comparison
95 if strings.TrimSpace(string(html)) != tc.expected {
96 t.Errorf("Expected %s, got %s", tc.expected, html)
97 }
98 })
99 }
100}
101
102func TestGhosttySupported(t *testing.T) {
103 // Save original environment variables
104 origTerm := os.Getenv("TERM")
105 origTermProgram := os.Getenv("TERM_PROGRAM")
106 origGhosttyResources := os.Getenv("GHOSTTY_RESOURCES_DIR")
107
108 // Restore environment variables after test
109 defer func() {
110 os.Setenv("TERM", origTerm)
111 os.Setenv("TERM_PROGRAM", origTermProgram)
112 os.Setenv("GHOSTTY_RESOURCES_DIR", origGhosttyResources)
113 }()
114
115 testCases := []struct {
116 name string
117 term string
118 termProgram string
119 ghosttyResourcesDir string
120 expected bool
121 }{
122 {
123 name: "No Ghostty environment variables",
124 term: "xterm",
125 termProgram: "",
126 ghosttyResourcesDir: "",
127 expected: false,
128 },
129 {
130 name: "TERM contains ghostty",
131 term: "xterm-ghostty",
132 termProgram: "",
133 ghosttyResourcesDir: "",
134 expected: true,
135 },
136 {
137 name: "TERM_PROGRAM is ghostty",
138 term: "xterm",
139 termProgram: "ghostty",
140 ghosttyResourcesDir: "",
141 expected: true,
142 },
143 {
144 name: "GHOSTTY_RESOURCES_DIR is set",
145 term: "xterm",
146 termProgram: "",
147 ghosttyResourcesDir: "/usr/share/ghostty",
148 expected: true,
149 },
150 {
151 name: "Multiple Ghostty indicators",
152 term: "ghostty",
153 termProgram: "ghostty",
154 ghosttyResourcesDir: "/usr/share/ghostty",
155 expected: true,
156 },
157 }
158
159 for _, tc := range testCases {
160 t.Run(tc.name, func(t *testing.T) {
161 os.Setenv("TERM", tc.term)
162 os.Setenv("TERM_PROGRAM", tc.termProgram)
163 os.Setenv("GHOSTTY_RESOURCES_DIR", tc.ghosttyResourcesDir)
164
165 result := ghosttySupported()
166 if result != tc.expected {
167 t.Errorf("Expected %t, got %t", tc.expected, result)
168 }
169 })
170 }
171}
172
173func TestImageProtocolSupported(t *testing.T) {
174 // Save original environment variables
175 origTerm := os.Getenv("TERM")
176 origKittyWindow := os.Getenv("KITTY_WINDOW_ID")
177 origTermProgram := os.Getenv("TERM_PROGRAM")
178 origGhosttyResources := os.Getenv("GHOSTTY_RESOURCES_DIR")
179 origItermlSession := os.Getenv("ITERM_SESSION_ID")
180 origWeztermExec := os.Getenv("WEZTERM_EXECUTABLE")
181 origWarpLocal := os.Getenv("WARP_IS_LOCAL_SHELL_SESSION")
182 origKonsoleDBus := os.Getenv("KONSOLE_DBUS_SESSION")
183
184 // Restore environment variables after test
185 defer func() {
186 os.Setenv("TERM", origTerm)
187 os.Setenv("KITTY_WINDOW_ID", origKittyWindow)
188 os.Setenv("TERM_PROGRAM", origTermProgram)
189 os.Setenv("GHOSTTY_RESOURCES_DIR", origGhosttyResources)
190 os.Setenv("ITERM_SESSION_ID", origItermlSession)
191 os.Setenv("WEZTERM_EXECUTABLE", origWeztermExec)
192 os.Setenv("WARP_IS_LOCAL_SHELL_SESSION", origWarpLocal)
193 os.Setenv("KONSOLE_DBUS_SESSION", origKonsoleDBus)
194 }()
195
196 testCases := []struct {
197 name string
198 setupEnv func()
199 clearAllEnv func()
200 expected bool
201 }{
202 {
203 name: "No supported terminals",
204 setupEnv: func() {
205 os.Setenv("TERM", "xterm")
206 os.Setenv("TERM_PROGRAM", "basic")
207 },
208 clearAllEnv: func() {
209 os.Unsetenv("KITTY_WINDOW_ID")
210 os.Unsetenv("GHOSTTY_RESOURCES_DIR")
211 os.Unsetenv("ITERM_SESSION_ID")
212 os.Unsetenv("WEZTERM_EXECUTABLE")
213 os.Unsetenv("WARP_IS_LOCAL_SHELL_SESSION")
214 os.Unsetenv("KONSOLE_DBUS_SESSION")
215 },
216 expected: false,
217 },
218 {
219 name: "Kitty supported via TERM",
220 setupEnv: func() {
221 os.Setenv("TERM", "xterm-kitty")
222 },
223 clearAllEnv: func() {
224 os.Unsetenv("KITTY_WINDOW_ID")
225 os.Unsetenv("GHOSTTY_RESOURCES_DIR")
226 os.Unsetenv("ITERM_SESSION_ID")
227 os.Unsetenv("WEZTERM_EXECUTABLE")
228 os.Unsetenv("WARP_IS_LOCAL_SHELL_SESSION")
229 os.Unsetenv("KONSOLE_DBUS_SESSION")
230 },
231 expected: true,
232 },
233 {
234 name: "Kitty supported via KITTY_WINDOW_ID",
235 setupEnv: func() {
236 os.Setenv("TERM", "xterm")
237 os.Setenv("KITTY_WINDOW_ID", "1")
238 },
239 clearAllEnv: func() {
240 os.Unsetenv("GHOSTTY_RESOURCES_DIR")
241 os.Unsetenv("ITERM_SESSION_ID")
242 os.Unsetenv("WEZTERM_EXECUTABLE")
243 os.Unsetenv("WARP_IS_LOCAL_SHELL_SESSION")
244 os.Unsetenv("KONSOLE_DBUS_SESSION")
245 },
246 expected: true,
247 },
248 {
249 name: "Ghostty supported via TERM_PROGRAM",
250 setupEnv: func() {
251 os.Setenv("TERM", "xterm")
252 os.Setenv("TERM_PROGRAM", "ghostty")
253 },
254 clearAllEnv: func() {
255 os.Unsetenv("KITTY_WINDOW_ID")
256 os.Unsetenv("GHOSTTY_RESOURCES_DIR")
257 os.Unsetenv("ITERM_SESSION_ID")
258 os.Unsetenv("WEZTERM_EXECUTABLE")
259 os.Unsetenv("WARP_IS_LOCAL_SHELL_SESSION")
260 os.Unsetenv("KONSOLE_DBUS_SESSION")
261 },
262 expected: true,
263 },
264 {
265 name: "iTerm2 supported via TERM_PROGRAM",
266 setupEnv: func() {
267 os.Setenv("TERM", "xterm")
268 os.Setenv("TERM_PROGRAM", "iterm.app")
269 },
270 clearAllEnv: func() {
271 os.Unsetenv("KITTY_WINDOW_ID")
272 os.Unsetenv("GHOSTTY_RESOURCES_DIR")
273 os.Unsetenv("ITERM_SESSION_ID")
274 os.Unsetenv("WEZTERM_EXECUTABLE")
275 os.Unsetenv("WARP_IS_LOCAL_SHELL_SESSION")
276 os.Unsetenv("KONSOLE_DBUS_SESSION")
277 },
278 expected: true,
279 },
280 {
281 name: "WezTerm supported via WEZTERM_EXECUTABLE",
282 setupEnv: func() {
283 os.Setenv("TERM", "xterm")
284 os.Setenv("WEZTERM_EXECUTABLE", "/usr/bin/wezterm")
285 },
286 clearAllEnv: func() {
287 os.Unsetenv("KITTY_WINDOW_ID")
288 os.Unsetenv("GHOSTTY_RESOURCES_DIR")
289 os.Unsetenv("ITERM_SESSION_ID")
290 os.Unsetenv("WARP_IS_LOCAL_SHELL_SESSION")
291 os.Unsetenv("KONSOLE_DBUS_SESSION")
292 },
293 expected: true,
294 },
295 {
296 name: "Warp supported via WARP_IS_LOCAL_SHELL_SESSION",
297 setupEnv: func() {
298 os.Setenv("TERM", "xterm")
299 os.Setenv("WARP_IS_LOCAL_SHELL_SESSION", "1")
300 },
301 clearAllEnv: func() {
302 os.Unsetenv("KITTY_WINDOW_ID")
303 os.Unsetenv("GHOSTTY_RESOURCES_DIR")
304 os.Unsetenv("ITERM_SESSION_ID")
305 os.Unsetenv("WEZTERM_EXECUTABLE")
306 os.Unsetenv("KONSOLE_DBUS_SESSION")
307 },
308 expected: true,
309 },
310 {
311 name: "Konsole supported via KONSOLE_DBUS_SESSION",
312 setupEnv: func() {
313 os.Setenv("TERM", "xterm")
314 os.Setenv("KONSOLE_DBUS_SESSION", "/Sessions/1")
315 },
316 clearAllEnv: func() {
317 os.Unsetenv("KITTY_WINDOW_ID")
318 os.Unsetenv("GHOSTTY_RESOURCES_DIR")
319 os.Unsetenv("ITERM_SESSION_ID")
320 os.Unsetenv("WEZTERM_EXECUTABLE")
321 os.Unsetenv("WARP_IS_LOCAL_SHELL_SESSION")
322 },
323 expected: true,
324 },
325 }
326
327 for _, tc := range testCases {
328 t.Run(tc.name, func(t *testing.T) {
329 tc.clearAllEnv()
330 tc.setupEnv()
331
332 result := imageProtocolSupported()
333 if result != tc.expected {
334 t.Errorf("Expected %t, got %t", tc.expected, result)
335 }
336 })
337 }
338}
339
340func TestHyperlinkSupported(t *testing.T) {
341 // Save original environment variables
342 origTerm := os.Getenv("TERM")
343 origTermProgram := os.Getenv("TERM_PROGRAM")
344 origVTEVersion := os.Getenv("VTE_VERSION")
345 origKittyWindow := os.Getenv("KITTY_WINDOW_ID")
346 origGhosttyResources := os.Getenv("GHOSTTY_RESOURCES_DIR")
347 origWeztermExec := os.Getenv("WEZTERM_EXECUTABLE")
348
349 // Restore environment variables after test
350 defer func() {
351 os.Setenv("TERM", origTerm)
352 os.Setenv("TERM_PROGRAM", origTermProgram)
353 os.Setenv("VTE_VERSION", origVTEVersion)
354 os.Setenv("KITTY_WINDOW_ID", origKittyWindow)
355 os.Setenv("GHOSTTY_RESOURCES_DIR", origGhosttyResources)
356 os.Setenv("WEZTERM_EXECUTABLE", origWeztermExec)
357 }()
358
359 testCases := []struct {
360 name string
361 setupEnv func()
362 clearAllEnv func()
363 expected bool
364 }{
365 {
366 name: "No hyperlink support",
367 setupEnv: func() {
368 os.Setenv("TERM", "xterm")
369 os.Setenv("TERM_PROGRAM", "basic")
370 },
371 clearAllEnv: func() {
372 os.Unsetenv("VTE_VERSION")
373 os.Unsetenv("KITTY_WINDOW_ID")
374 os.Unsetenv("GHOSTTY_RESOURCES_DIR")
375 os.Unsetenv("WEZTERM_EXECUTABLE")
376 },
377 expected: false,
378 },
379 {
380 name: "Kitty hyperlink support via TERM",
381 setupEnv: func() {
382 os.Setenv("TERM", "xterm-kitty")
383 },
384 clearAllEnv: func() {
385 os.Unsetenv("VTE_VERSION")
386 os.Unsetenv("KITTY_WINDOW_ID")
387 os.Unsetenv("GHOSTTY_RESOURCES_DIR")
388 os.Unsetenv("WEZTERM_EXECUTABLE")
389 },
390 expected: true,
391 },
392 {
393 name: "VTE-based terminal hyperlink support",
394 setupEnv: func() {
395 os.Setenv("TERM", "xterm")
396 os.Setenv("VTE_VERSION", "0.60.3")
397 },
398 clearAllEnv: func() {
399 os.Unsetenv("KITTY_WINDOW_ID")
400 os.Unsetenv("GHOSTTY_RESOURCES_DIR")
401 os.Unsetenv("WEZTERM_EXECUTABLE")
402 },
403 expected: true,
404 },
405 {
406 name: "iTerm2 hyperlink support",
407 setupEnv: func() {
408 os.Setenv("TERM", "xterm")
409 os.Setenv("TERM_PROGRAM", "iterm.app")
410 },
411 clearAllEnv: func() {
412 os.Unsetenv("VTE_VERSION")
413 os.Unsetenv("KITTY_WINDOW_ID")
414 os.Unsetenv("GHOSTTY_RESOURCES_DIR")
415 os.Unsetenv("WEZTERM_EXECUTABLE")
416 },
417 expected: true,
418 },
419 {
420 name: "WezTerm hyperlink support",
421 setupEnv: func() {
422 os.Setenv("TERM", "xterm")
423 os.Setenv("WEZTERM_EXECUTABLE", "/usr/bin/wezterm")
424 },
425 clearAllEnv: func() {
426 os.Unsetenv("VTE_VERSION")
427 os.Unsetenv("KITTY_WINDOW_ID")
428 os.Unsetenv("GHOSTTY_RESOURCES_DIR")
429 },
430 expected: true,
431 },
432 }
433
434 for _, tc := range testCases {
435 t.Run(tc.name, func(t *testing.T) {
436 tc.clearAllEnv()
437 tc.setupEnv()
438
439 result := hyperlinkSupported()
440 if result != tc.expected {
441 t.Errorf("Expected %t, got %t", tc.expected, result)
442 }
443 })
444 }
445}
446
447func TestProcessBodyWithHyperlinkSupport(t *testing.T) {
448 // Save original environment variables
449 origTerm := os.Getenv("TERM")
450 origTermProgram := os.Getenv("TERM_PROGRAM")
451 origVTEVersion := os.Getenv("VTE_VERSION")
452 origKittyWindow := os.Getenv("KITTY_WINDOW_ID")
453
454 // Restore environment variables after test
455 defer func() {
456 os.Setenv("TERM", origTerm)
457 os.Setenv("TERM_PROGRAM", origTermProgram)
458 os.Setenv("VTE_VERSION", origVTEVersion)
459 os.Setenv("KITTY_WINDOW_ID", origKittyWindow)
460 }()
461
462 h1Style := lipgloss.NewStyle().SetString("H1")
463 h2Style := lipgloss.NewStyle().SetString("H2")
464 bodyStyle := lipgloss.NewStyle().SetString("BODY")
465
466 testCases := []struct {
467 name string
468 setupHyperlinks func()
469 input string
470 expectedContains string
471 expectedNotContains string
472 }{
473 {
474 name: "Link with hyperlink support",
475 setupHyperlinks: func() {
476 os.Setenv("TERM", "xterm-kitty")
477 os.Unsetenv("VTE_VERSION")
478 os.Unsetenv("KITTY_WINDOW_ID")
479 },
480 input: `<a href="http://example.com">Click here</a>`,
481 expectedContains: "Click here",
482 expectedNotContains: "<http://example.com>",
483 },
484 {
485 name: "Link without hyperlink support",
486 setupHyperlinks: func() {
487 clearAllTerminalEnv()
488 },
489 input: `<a href="http://example.com">Click here</a>`,
490 expectedContains: "Click here <http://example.com>",
491 },
492 {
493 name: "Image link with hyperlink support",
494 setupHyperlinks: func() {
495 os.Setenv("TERM", "xterm")
496 os.Setenv("VTE_VERSION", "0.60.3")
497 os.Unsetenv("KITTY_WINDOW_ID")
498 },
499 input: `<img src="http://example.com/img.png" alt="alt text">`,
500 expectedContains: "[Click here to view image: alt text]",
501 expectedNotContains: "<http://example.com/img.png>",
502 },
503 {
504 name: "Image link without hyperlink support",
505 setupHyperlinks: func() {
506 clearAllTerminalEnv()
507 },
508 input: `<img src="http://example.com/img.png" alt="alt text">`,
509 expectedContains: "[Image: alt text, http://example.com/img.png]",
510 },
511 }
512
513 // Regex to strip out ANSI SGR escape codes (e.g. \x1b[38;2;...m)
514 ansiEscapeRegex := regexp.MustCompile(`\x1b\[[0-9;]*m`)
515
516 for _, tc := range testCases {
517 t.Run(tc.name, func(t *testing.T) {
518 tc.setupHyperlinks()
519
520 processed, _, err := ProcessBody(tc.input, h1Style, h2Style, bodyStyle, false)
521 if err != nil {
522 t.Fatalf("ProcessBody() failed: %v", err)
523 }
524
525 cleanProcessed := ansiEscapeRegex.ReplaceAllString(processed, "")
526
527 if !strings.Contains(cleanProcessed, tc.expectedContains) {
528 t.Errorf("Processed body does not contain expected text.\nGot: %q\nWant to contain: %q", cleanProcessed, tc.expectedContains)
529 }
530
531 if tc.expectedNotContains != "" && strings.Contains(cleanProcessed, tc.expectedNotContains) {
532 t.Errorf("Processed body contains unexpected text.\nGot: %q\nShould not contain: %q", cleanProcessed, tc.expectedNotContains)
533 }
534 })
535 }
536}
537
538func TestProcessBodyWithImageProtocol(t *testing.T) {
539 // Save original environment variables
540 origTerm := os.Getenv("TERM")
541 origTermProgram := os.Getenv("TERM_PROGRAM")
542 origKittyWindow := os.Getenv("KITTY_WINDOW_ID")
543 origGhosttyResources := os.Getenv("GHOSTTY_RESOURCES_DIR")
544 origItermlSession := os.Getenv("ITERM_SESSION_ID")
545 origWeztermExec := os.Getenv("WEZTERM_EXECUTABLE")
546
547 // Restore environment variables after test
548 defer func() {
549 os.Setenv("TERM", origTerm)
550 os.Setenv("TERM_PROGRAM", origTermProgram)
551 os.Setenv("KITTY_WINDOW_ID", origKittyWindow)
552 os.Setenv("GHOSTTY_RESOURCES_DIR", origGhosttyResources)
553 os.Setenv("ITERM_SESSION_ID", origItermlSession)
554 os.Setenv("WEZTERM_EXECUTABLE", origWeztermExec)
555 }()
556
557 h1Style := lipgloss.NewStyle().SetString("H1")
558 h2Style := lipgloss.NewStyle().SetString("H2")
559 bodyStyle := lipgloss.NewStyle().SetString("BODY")
560
561 // Create a simple base64 PNG image (1x1 pixel white PNG)
562 testBase64PNG := "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8/5+hHgAHggJ/PchI7wAAAABJRU5ErkJggg=="
563
564 testCases := []struct {
565 name string
566 setupImageProtocol func()
567 clearAllImageEnv func()
568 input string
569 expectedContains string
570 expectedNotContains string
571 expectPlacements bool
572 }{
573 {
574 name: "Data URI image with Kitty support returns placement",
575 setupImageProtocol: func() {
576 os.Setenv("TERM", "xterm-kitty")
577 },
578 clearAllImageEnv: func() {
579 os.Unsetenv("KITTY_WINDOW_ID")
580 os.Unsetenv("GHOSTTY_RESOURCES_DIR")
581 os.Unsetenv("ITERM_SESSION_ID")
582 os.Unsetenv("WEZTERM_EXECUTABLE")
583 },
584 input: `<img src="data:image/png;base64,` + testBase64PNG + `" alt="test image">`,
585 expectedNotContains: "[Image: test image,",
586 expectPlacements: true,
587 },
588 {
589 name: "Data URI image with iTerm2 support returns placement",
590 setupImageProtocol: func() {
591 os.Setenv("TERM", "xterm")
592 os.Setenv("TERM_PROGRAM", "iterm.app")
593 },
594 clearAllImageEnv: func() {
595 os.Unsetenv("KITTY_WINDOW_ID")
596 os.Unsetenv("GHOSTTY_RESOURCES_DIR")
597 os.Unsetenv("ITERM_SESSION_ID")
598 os.Unsetenv("WEZTERM_EXECUTABLE")
599 },
600 input: `<img src="data:image/png;base64,` + testBase64PNG + `" alt="test image">`,
601 expectedNotContains: "[Image: test image,",
602 expectPlacements: true,
603 },
604 {
605 name: "Data URI image without protocol support",
606 setupImageProtocol: func() {
607 clearAllTerminalEnv()
608 },
609 clearAllImageEnv: func() {
610 // This is handled by clearAllTerminalEnv now
611 },
612 input: `<img src="data:image/png;base64,` + testBase64PNG + `" alt="test image">`,
613 expectedContains: "[Image: test image,",
614 },
615 {
616 name: "Remote image with WezTerm support (has hyperlink support)",
617 setupImageProtocol: func() {
618 clearAllTerminalEnv()
619 os.Setenv("WEZTERM_EXECUTABLE", "/usr/bin/wezterm")
620 },
621 clearAllImageEnv: func() {
622 // This is handled by clearAllTerminalEnv now
623 },
624 input: `<img src="http://example.com/img.png" alt="remote image">`,
625 expectedContains: "[Click here to view image: remote image]", // Remote images won't render without actual fetch, but hyperlinks work
626 },
627 {
628 name: "Remote image without protocol support",
629 setupImageProtocol: func() {
630 clearAllTerminalEnv()
631 },
632 clearAllImageEnv: func() {
633 // This is handled by clearAllTerminalEnv now
634 },
635 input: `<img src="http://example.com/img.png" alt="remote image">`,
636 expectedContains: "[Image: remote image,",
637 },
638 }
639
640 ansiEscapeRegex := regexp.MustCompile(`\x1b\[[0-9;]*m`)
641
642 for _, tc := range testCases {
643 t.Run(tc.name, func(t *testing.T) {
644 tc.clearAllImageEnv()
645 tc.setupImageProtocol()
646
647 processed, placements, err := ProcessBody(tc.input, h1Style, h2Style, bodyStyle, false)
648 if err != nil {
649 t.Fatalf("ProcessBody() failed: %v", err)
650 }
651
652 if tc.expectPlacements {
653 if len(placements) == 0 {
654 t.Errorf("Expected image placements but got none")
655 } else {
656 if placements[0].Base64 == "" {
657 t.Errorf("Expected non-empty Base64 in placement")
658 }
659 if placements[0].Rows < 1 {
660 t.Errorf("Expected Rows >= 1, got %d", placements[0].Rows)
661 }
662 }
663 }
664
665 cleanProcessed := ansiEscapeRegex.ReplaceAllString(processed, "")
666
667 if tc.expectedContains != "" && !strings.Contains(cleanProcessed, tc.expectedContains) {
668 t.Errorf("Processed body does not contain expected text.\nGot: %q\nWant to contain: %q", cleanProcessed, tc.expectedContains)
669 }
670
671 if tc.expectedNotContains != "" && strings.Contains(cleanProcessed, tc.expectedNotContains) {
672 t.Errorf("Processed body contains unexpected text.\nGot: %q\nShould not contain: %q", cleanProcessed, tc.expectedNotContains)
673 }
674 })
675 }
676}
677
678func TestProcessBody(t *testing.T) {
679 h1Style := lipgloss.NewStyle().SetString("H1")
680 h2Style := lipgloss.NewStyle().SetString("H2")
681 bodyStyle := lipgloss.NewStyle().SetString("BODY")
682
683 testCases := []struct {
684 name string
685 input string
686 expected string
687 }{
688 {
689 name: "Simple HTML",
690 input: "<p>Hello, world!</p>",
691 expected: "Hello, world!",
692 },
693 {
694 name: "With headers HTML",
695 input: "<h1>Header 1</h1>",
696 expected: "Header 1",
697 },
698 {
699 name: "With headers Markdown",
700 input: "# Header 1",
701 expected: "Header 1",
702 },
703 {
704 name: "Plain text",
705 input: "Just plain text without any markup",
706 expected: "Just plain text without any markup",
707 },
708 }
709
710 ansiEscapeRegex := regexp.MustCompile(`\x1b\[[0-9;]*m`)
711
712 for _, tc := range testCases {
713 t.Run(tc.name, func(t *testing.T) {
714 processed, _, err := ProcessBody(tc.input, h1Style, h2Style, bodyStyle, false)
715 if err != nil {
716 t.Fatalf("ProcessBody() failed: %v", err)
717 }
718
719 cleanProcessed := ansiEscapeRegex.ReplaceAllString(processed, "")
720
721 if !strings.Contains(cleanProcessed, tc.expected) {
722 t.Errorf("Processed body does not contain expected text.\nGot: %q\nWant to contain: %q", cleanProcessed, tc.expected)
723 }
724 })
725 }
726}