html_test.go

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