html_test.go

  1package view
  2
  3import (
  4	"os"
  5	"strings"
  6	"testing"
  7
  8	"github.com/charmbracelet/lipgloss"
  9)
 10
 11func TestDecodeQuotedPrintable(t *testing.T) {
 12	testCases := []struct {
 13		name     string
 14		input    string
 15		expected string
 16	}{
 17		{
 18			name:     "Simple case",
 19			input:    "Hello=2C world=21",
 20			expected: "Hello, world!",
 21		},
 22		{
 23			name:     "With soft line break",
 24			input:    "This is a long line that gets wrapped=\r\n and continues here.",
 25			expected: "This is a long line that gets wrapped and continues here.",
 26		},
 27		{
 28			name:     "No encoding",
 29			input:    "Just a plain string.",
 30			expected: "Just a plain string.",
 31		},
 32	}
 33
 34	for _, tc := range testCases {
 35		t.Run(tc.name, func(t *testing.T) {
 36			decoded, err := decodeQuotedPrintable(tc.input)
 37			if err != nil {
 38				t.Fatalf("decodeQuotedPrintable() failed: %v", err)
 39			}
 40			if decoded != tc.expected {
 41				t.Errorf("Expected %q, got %q", tc.expected, decoded)
 42			}
 43		})
 44	}
 45}
 46
 47func TestMarkdownToHTML(t *testing.T) {
 48	testCases := []struct {
 49		name     string
 50		input    string
 51		expected string
 52	}{
 53		{
 54			name:     "Heading",
 55			input:    "# Hello",
 56			expected: "<h1>Hello</h1>",
 57		},
 58		{
 59			name:     "Bold",
 60			input:    "**bold text**",
 61			expected: "<p><strong>bold text</strong></p>",
 62		},
 63		{
 64			name:     "Link",
 65			input:    "[link](http://example.com)",
 66			expected: `<p><a href="http://example.com">link</a></p>`,
 67		},
 68	}
 69
 70	for _, tc := range testCases {
 71		t.Run(tc.name, func(t *testing.T) {
 72			html := markdownToHTML([]byte(tc.input))
 73			// Trim newlines for consistent comparison
 74			if strings.TrimSpace(string(html)) != tc.expected {
 75				t.Errorf("Expected %s, got %s", tc.expected, html)
 76			}
 77		})
 78	}
 79}
 80
 81func TestGhosttySupported(t *testing.T) {
 82	// Save original environment variables
 83	origTerm := os.Getenv("TERM")
 84	origTermProgram := os.Getenv("TERM_PROGRAM")
 85	origGhosttyResources := os.Getenv("GHOSTTY_RESOURCES_DIR")
 86
 87	// Restore environment variables after test
 88	defer func() {
 89		os.Setenv("TERM", origTerm)
 90		os.Setenv("TERM_PROGRAM", origTermProgram)
 91		os.Setenv("GHOSTTY_RESOURCES_DIR", origGhosttyResources)
 92	}()
 93
 94	testCases := []struct {
 95		name                string
 96		term                string
 97		termProgram         string
 98		ghosttyResourcesDir string
 99		expected            bool
100	}{
101		{
102			name:                "No Ghostty environment variables",
103			term:                "xterm",
104			termProgram:         "",
105			ghosttyResourcesDir: "",
106			expected:            false,
107		},
108		{
109			name:                "TERM contains ghostty",
110			term:                "xterm-ghostty",
111			termProgram:         "",
112			ghosttyResourcesDir: "",
113			expected:            true,
114		},
115		{
116			name:                "TERM_PROGRAM is ghostty",
117			term:                "xterm",
118			termProgram:         "ghostty",
119			ghosttyResourcesDir: "",
120			expected:            true,
121		},
122		{
123			name:                "GHOSTTY_RESOURCES_DIR is set",
124			term:                "xterm",
125			termProgram:         "",
126			ghosttyResourcesDir: "/usr/share/ghostty",
127			expected:            true,
128		},
129		{
130			name:                "Multiple Ghostty indicators",
131			term:                "ghostty",
132			termProgram:         "ghostty",
133			ghosttyResourcesDir: "/usr/share/ghostty",
134			expected:            true,
135		},
136	}
137
138	for _, tc := range testCases {
139		t.Run(tc.name, func(t *testing.T) {
140			os.Setenv("TERM", tc.term)
141			os.Setenv("TERM_PROGRAM", tc.termProgram)
142			os.Setenv("GHOSTTY_RESOURCES_DIR", tc.ghosttyResourcesDir)
143
144			result := ghosttySupported()
145			if result != tc.expected {
146				t.Errorf("Expected %t, got %t", tc.expected, result)
147			}
148		})
149	}
150}
151
152func TestImageProtocolSupported(t *testing.T) {
153	// Save original environment variables
154	origTerm := os.Getenv("TERM")
155	origKittyWindow := os.Getenv("KITTY_WINDOW_ID")
156	origTermProgram := os.Getenv("TERM_PROGRAM")
157	origGhosttyResources := os.Getenv("GHOSTTY_RESOURCES_DIR")
158
159	// Restore environment variables after test
160	defer func() {
161		os.Setenv("TERM", origTerm)
162		os.Setenv("KITTY_WINDOW_ID", origKittyWindow)
163		os.Setenv("TERM_PROGRAM", origTermProgram)
164		os.Setenv("GHOSTTY_RESOURCES_DIR", origGhosttyResources)
165	}()
166
167	testCases := []struct {
168		name                string
169		term                string
170		kittyWindow         string
171		termProgram         string
172		ghosttyResourcesDir string
173		expected            bool
174	}{
175		{
176			name:                "No supported terminals",
177			term:                "xterm",
178			kittyWindow:         "",
179			termProgram:         "",
180			ghosttyResourcesDir: "",
181			expected:            false,
182		},
183		{
184			name:                "Kitty supported",
185			term:                "xterm-kitty",
186			kittyWindow:         "",
187			termProgram:         "",
188			ghosttyResourcesDir: "",
189			expected:            true,
190		},
191		{
192			name:                "Ghostty supported",
193			term:                "xterm",
194			kittyWindow:         "",
195			termProgram:         "ghostty",
196			ghosttyResourcesDir: "",
197			expected:            true,
198		},
199		{
200			name:                "Both Kitty and Ghostty supported",
201			term:                "xterm-kitty",
202			kittyWindow:         "1",
203			termProgram:         "ghostty",
204			ghosttyResourcesDir: "/usr/share/ghostty",
205			expected:            true,
206		},
207	}
208
209	for _, tc := range testCases {
210		t.Run(tc.name, func(t *testing.T) {
211			os.Setenv("TERM", tc.term)
212			os.Setenv("KITTY_WINDOW_ID", tc.kittyWindow)
213			os.Setenv("TERM_PROGRAM", tc.termProgram)
214			os.Setenv("GHOSTTY_RESOURCES_DIR", tc.ghosttyResourcesDir)
215
216			result := imageProtocolSupported()
217			if result != tc.expected {
218				t.Errorf("Expected %t, got %t", tc.expected, result)
219			}
220		})
221	}
222}
223
224func TestProcessBody(t *testing.T) {
225	h1Style := lipgloss.NewStyle().SetString("H1")
226	h2Style := lipgloss.NewStyle().SetString("H2")
227	bodyStyle := lipgloss.NewStyle().SetString("BODY")
228
229	testCases := []struct {
230		name     string
231		input    string
232		expected string
233	}{
234		{
235			name:     "Simple HTML",
236			input:    "<p>Hello, world!</p>",
237			expected: "Hello, world!",
238		},
239		{
240			name:     "With link HTML",
241			input:    `<a href="http://example.com">Click here</a>`,
242			expected: "Click here",
243		},
244		{
245			name:     "With image HTML",
246			input:    `<img src="http://example.com/img.png" alt="alt text">`,
247			expected: "[Click here to view image: alt text]",
248		},
249		{
250			name:     "With headers HTML",
251			input:    "<h1>Header 1</h1>",
252			expected: "Header 1",
253		},
254		{
255			name:     "With link Markdown",
256			input:    `[Click here](http://example.com)`,
257			expected: "Click here",
258		},
259		{
260			name:     "With image Markdown",
261			input:    `![alt text](http://example.com/img.png)>`,
262			expected: "[Click here to view image: alt text]",
263		},
264		{
265			name:     "With headers Markdown",
266			input:    "# Header 1",
267			expected: "Header 1",
268		},
269	}
270
271	for _, tc := range testCases {
272		t.Run(tc.name, func(t *testing.T) {
273			processed, err := ProcessBody(tc.input, h1Style, h2Style, bodyStyle)
274			if err != nil {
275				t.Fatalf("ProcessBody() failed: %v", err)
276			}
277			// Use Contains because styles add ANSI codes
278			if !strings.Contains(processed, tc.expected) {
279				t.Errorf("Processed body does not contain expected text.\nGot: %q\nWant to contain: %q", processed, tc.expected)
280			}
281		})
282	}
283}