Skip to content

Commit cba45ba

Browse files
committed
test(tui): test code highlighting
1 parent af9379b commit cba45ba

File tree

1 file changed

+333
-0
lines changed

1 file changed

+333
-0
lines changed

internal/tui/highlight_test.go

Lines changed: 333 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,333 @@
1+
package tui
2+
3+
import (
4+
"strings"
5+
"testing"
6+
7+
"github.com/alecthomas/chroma/v2"
8+
"github.com/alecthomas/chroma/v2/lexers"
9+
"github.com/alecthomas/chroma/v2/styles"
10+
)
11+
12+
func TestParseHighlightSyntax(t *testing.T) {
13+
tests := []struct {
14+
name string
15+
syntax string
16+
expected []LineRange
17+
}{
18+
{
19+
name: "empty syntax",
20+
syntax: "",
21+
expected: nil,
22+
},
23+
{
24+
name: "single line",
25+
syntax: "{5}",
26+
expected: []LineRange{{Start: 5, End: 5}},
27+
},
28+
{
29+
name: "single range",
30+
syntax: "{1-3}",
31+
expected: []LineRange{{Start: 1, End: 3}},
32+
},
33+
{
34+
name: "multiple lines",
35+
syntax: "{1,3,5}",
36+
expected: []LineRange{{Start: 1, End: 1}, {Start: 3, End: 3}, {Start: 5, End: 5}},
37+
},
38+
{
39+
name: "multiple ranges",
40+
syntax: "{1-3,7-9}",
41+
expected: []LineRange{{Start: 1, End: 3}, {Start: 7, End: 9}},
42+
},
43+
{
44+
name: "mixed lines and ranges",
45+
syntax: "{1,3-5,7}",
46+
expected: []LineRange{{Start: 1, End: 1}, {Start: 3, End: 5}, {Start: 7, End: 7}},
47+
},
48+
{
49+
name: "with spaces",
50+
syntax: "{ 1 - 3 , 5 }",
51+
expected: []LineRange{{Start: 1, End: 3}, {Start: 5, End: 5}},
52+
},
53+
{
54+
name: "invalid range (start > end)",
55+
syntax: "{5-3}",
56+
expected: nil,
57+
},
58+
{
59+
name: "invalid syntax",
60+
syntax: "{abc}",
61+
expected: nil,
62+
},
63+
{
64+
name: "without braces",
65+
syntax: "1-3,5",
66+
expected: []LineRange{{Start: 1, End: 3}, {Start: 5, End: 5}},
67+
},
68+
}
69+
70+
for _, tt := range tests {
71+
t.Run(tt.name, func(t *testing.T) {
72+
result := parseHighlightSyntax(tt.syntax)
73+
if len(result) != len(tt.expected) {
74+
t.Errorf("parseHighlightSyntax(%q) returned %d ranges, expected %d",
75+
tt.syntax, len(result), len(tt.expected))
76+
return
77+
}
78+
79+
for i, r := range result {
80+
if i >= len(tt.expected) || r.Start != tt.expected[i].Start || r.End != tt.expected[i].End {
81+
t.Errorf("parseHighlightSyntax(%q) = %+v, expected %+v",
82+
tt.syntax, result, tt.expected)
83+
break
84+
}
85+
}
86+
})
87+
}
88+
}
89+
90+
func TestShouldHighlightLine(t *testing.T) {
91+
ranges := []LineRange{
92+
{Start: 1, End: 3},
93+
{Start: 5, End: 5},
94+
{Start: 7, End: 10},
95+
}
96+
97+
tests := []struct {
98+
lineNum int
99+
expected bool
100+
}{
101+
{1, true}, // in first range
102+
{2, true}, // in first range
103+
{3, true}, // in first range
104+
{4, false}, // not in any range
105+
{5, true}, // in second range
106+
{6, false}, // not in any range
107+
{7, true}, // in third range
108+
{8, true}, // in third range
109+
{9, true}, // in third range
110+
{10, true}, // in third range
111+
{11, false}, // not in any range
112+
{0, false}, // edge case
113+
}
114+
115+
for _, tt := range tests {
116+
t.Run(string(rune(tt.lineNum)), func(t *testing.T) {
117+
result := shouldHighlightLine(tt.lineNum, ranges)
118+
if result != tt.expected {
119+
t.Errorf("shouldHighlightLine(%d, ranges) = %v, expected %v",
120+
tt.lineNum, result, tt.expected)
121+
}
122+
})
123+
}
124+
}
125+
126+
func TestShouldHighlightLineEmptyRanges(t *testing.T) {
127+
var emptyRanges []LineRange
128+
129+
result := shouldHighlightLine(5, emptyRanges)
130+
if result != false {
131+
t.Errorf("shouldHighlightLine(5, emptyRanges) = %v, expected false", result)
132+
}
133+
}
134+
135+
func TestRenderCustomCodeBlock(t *testing.T) {
136+
tests := []struct {
137+
name string
138+
content string
139+
info CodeHighlightInfo
140+
themeName string
141+
}{
142+
{
143+
name: "simple code with highlighting",
144+
content: "console.log('hello');\nconsole.log('world');",
145+
info: CodeHighlightInfo{
146+
Language: "javascript",
147+
Ranges: []LineRange{{Start: 1, End: 1}},
148+
},
149+
themeName: "dark",
150+
},
151+
{
152+
name: "python code with multiple ranges",
153+
content: "def hello():\n print('hello')\n print('world')\n return True",
154+
info: CodeHighlightInfo{
155+
Language: "python",
156+
Ranges: []LineRange{{Start: 1, End: 2}, {Start: 4, End: 4}},
157+
},
158+
themeName: "dark",
159+
},
160+
{
161+
name: "unknown language fallback",
162+
content: "some random text\nmore text",
163+
info: CodeHighlightInfo{
164+
Language: "unknownlang",
165+
Ranges: []LineRange{{Start: 1, End: 1}},
166+
},
167+
themeName: "dark",
168+
},
169+
}
170+
171+
for _, tt := range tests {
172+
t.Run(tt.name, func(t *testing.T) {
173+
result := renderCustomCodeBlock(tt.content, tt.info, tt.themeName)
174+
175+
// Basic validation - should not be empty
176+
if result == "" {
177+
t.Error("renderCustomCodeBlock returned empty string")
178+
}
179+
180+
// Result should have meaningful content (more than just whitespace)
181+
if len(strings.TrimSpace(result)) == 0 {
182+
t.Error("renderCustomCodeBlock returned only whitespace")
183+
}
184+
185+
// For known languages, check that it contains some recognizable elements
186+
if tt.info.Language == "javascript" {
187+
if !strings.Contains(result, "console") && !strings.Contains(result, "log") {
188+
t.Error("renderCustomCodeBlock result doesn't contain expected javascript elements")
189+
}
190+
} else if tt.info.Language == "python" {
191+
if !strings.Contains(result, "def") && !strings.Contains(result, "print") {
192+
t.Error("renderCustomCodeBlock result doesn't contain expected python elements")
193+
}
194+
}
195+
})
196+
}
197+
}
198+
199+
func TestRenderWithStyle(t *testing.T) {
200+
content := "console.log('test');\nvar x = 1;"
201+
info := CodeHighlightInfo{
202+
Language: "javascript",
203+
Ranges: []LineRange{{Start: 1, End: 1}},
204+
}
205+
206+
lexer := lexers.Get("javascript")
207+
if lexer == nil {
208+
lexer = lexers.Fallback
209+
}
210+
lexer = chroma.Coalesce(lexer)
211+
212+
style := styles.Get("github")
213+
if style == nil {
214+
style = styles.Fallback
215+
}
216+
217+
result := renderWithStyle(content, info, lexer, style)
218+
219+
if result == "" {
220+
t.Error("renderWithStyle returned empty string")
221+
}
222+
223+
// Result should have meaningful content (more than just whitespace)
224+
if len(strings.TrimSpace(result)) == 0 {
225+
t.Error("renderWithStyle returned only whitespace")
226+
}
227+
228+
// Should contain some recognizable elements from the original content
229+
if !strings.Contains(result, "console") && !strings.Contains(result, "log") && !strings.Contains(result, "var") {
230+
t.Error("renderWithStyle result doesn't contain expected javascript elements")
231+
}
232+
}
233+
234+
func TestProcessMarkdownWithHighlighting(t *testing.T) {
235+
tests := []struct {
236+
name string
237+
markdown string
238+
theme string
239+
wantErr bool
240+
}{
241+
{
242+
name: "markdown without code blocks",
243+
markdown: "# Hello\n\nThis is regular markdown text.",
244+
theme: "dark",
245+
wantErr: false,
246+
},
247+
{
248+
name: "markdown with simple code block",
249+
markdown: "# Code Example\n\n```javascript\nconsole.log('hello');\n```",
250+
theme: "dark",
251+
wantErr: false,
252+
},
253+
{
254+
name: "markdown with highlighted code block",
255+
markdown: "# Code Example\n\n```javascript{1}\nconsole.log('hello');\nconsole.log('world');\n```",
256+
theme: "dark",
257+
wantErr: false,
258+
},
259+
{
260+
name: "markdown with multiple code blocks",
261+
markdown: "# Examples\n\n```javascript{1}\nconsole.log('hello');\n```\n\n```python{2}\nprint('hello')\nprint('world')\n```",
262+
theme: "dark",
263+
wantErr: false,
264+
},
265+
{
266+
name: "markdown with range highlighting",
267+
markdown: "```python{1-2}\ndef hello():\n print('hello')\n return True\n```",
268+
theme: "dark",
269+
wantErr: false,
270+
},
271+
{
272+
name: "markdown with mixed content",
273+
markdown: "# Title\n\nSome text before.\n\n```javascript{1}\nconsole.log('test');\n```\n\nSome text after.",
274+
theme: "dark",
275+
wantErr: false,
276+
},
277+
}
278+
279+
for _, tt := range tests {
280+
t.Run(tt.name, func(t *testing.T) {
281+
result, err := processMarkdownWithHighlighting(tt.markdown, tt.theme)
282+
283+
if (err != nil) != tt.wantErr {
284+
t.Errorf("processMarkdownWithHighlighting() error = %v, wantErr %v", err, tt.wantErr)
285+
return
286+
}
287+
288+
if result == "" {
289+
t.Error("processMarkdownWithHighlighting returned empty string")
290+
}
291+
292+
// For code blocks with highlighting, the result should be processed
293+
if strings.Contains(tt.markdown, "{") && strings.Contains(tt.markdown, "}") {
294+
// Should contain some processed content
295+
if len(result) < len(tt.markdown)/2 {
296+
t.Error("processMarkdownWithHighlighting result seems too short for highlighted content")
297+
}
298+
}
299+
})
300+
}
301+
}
302+
303+
func TestCodeHighlightInfo(t *testing.T) {
304+
info := CodeHighlightInfo{
305+
Language: "javascript",
306+
Ranges: []LineRange{{Start: 1, End: 3}, {Start: 5, End: 5}},
307+
}
308+
309+
if info.Language != "javascript" {
310+
t.Errorf("Expected language 'javascript', got '%s'", info.Language)
311+
}
312+
313+
if len(info.Ranges) != 2 {
314+
t.Errorf("Expected 2 ranges, got %d", len(info.Ranges))
315+
}
316+
317+
if info.Ranges[0].Start != 1 || info.Ranges[0].End != 3 {
318+
t.Errorf("First range incorrect: got {%d, %d}, expected {1, 3}",
319+
info.Ranges[0].Start, info.Ranges[0].End)
320+
}
321+
}
322+
323+
func TestLineRange(t *testing.T) {
324+
lr := LineRange{Start: 1, End: 5}
325+
326+
if lr.Start != 1 {
327+
t.Errorf("Expected Start = 1, got %d", lr.Start)
328+
}
329+
330+
if lr.End != 5 {
331+
t.Errorf("Expected End = 5, got %d", lr.End)
332+
}
333+
}

0 commit comments

Comments
 (0)