1 package html
2
3 import (
4 "bytes"
5 "fmt"
6 "io/ioutil"
7 "strings"
8 "testing"
9
10 "github.com/stretchr/testify/assert"
11
12 "github.com/alecthomas/chroma"
13 "github.com/alecthomas/chroma/lexers"
14 "github.com/alecthomas/chroma/styles"
15 )
16
17 func TestCompressStyle(t *testing.T) {
18 style := "color: #888888; background-color: #faffff"
19 actual := compressStyle(style)
20 expected := "color:#888;background-color:#faffff"
21 assert.Equal(t, expected, actual)
22 }
23
24 func BenchmarkHTMLFormatter(b *testing.B) {
25 formatter := New()
26 b.ResetTimer()
27 for i := 0; i < b.N; i++ {
28 it, err := lexers.Get("go").Tokenise(nil, "package main\nfunc main()\n{\nprintln(`hello world`)\n}\n")
29 assert.NoError(b, err)
30 err = formatter.Format(ioutil.Discard, styles.Fallback, it)
31 assert.NoError(b, err)
32 }
33 }
34
35 func TestSplitTokensIntoLines(t *testing.T) {
36 in := []chroma.Token{
37 {Value: "hello", Type: chroma.NameKeyword},
38 {Value: " world\nwhat?\n", Type: chroma.NameKeyword},
39 }
40 expected := [][]chroma.Token{
41 {
42 {Type: chroma.NameKeyword, Value: "hello"},
43 {Type: chroma.NameKeyword, Value: " world\n"},
44 },
45 {
46 {Type: chroma.NameKeyword, Value: "what?\n"},
47 },
48 }
49 actual := chroma.SplitTokensIntoLines(in)
50 assert.Equal(t, expected, actual)
51 }
52
53 func TestFormatterStyleToCSS(t *testing.T) {
54 builder := styles.Get("github").Builder()
55 builder.Add(chroma.LineHighlight, "bg:#ffffcc")
56 builder.Add(chroma.LineNumbers, "bold")
57 style, err := builder.Build()
58 if err != nil {
59 t.Error(err)
60 }
61 formatter := New(WithClasses(true))
62 css := formatter.styleToCSS(style)
63 for _, s := range css {
64 if strings.HasPrefix(strings.TrimSpace(s), ";") {
65 t.Errorf("rule starts with semicolon - expected valid css rule without semicolon: %v", s)
66 }
67 }
68 }
69
70 func TestClassPrefix(t *testing.T) {
71 wantPrefix := "some-prefix-"
72 withPrefix := New(WithClasses(true), ClassPrefix(wantPrefix))
73 noPrefix := New(WithClasses(true))
74 for st := range chroma.StandardTypes {
75 if noPrefix.class(st) == "" {
76 if got := withPrefix.class(st); got != "" {
77 t.Errorf("Formatter.class(%v): prefix shouldn't be added to empty classes", st)
78 }
79 } else if got := withPrefix.class(st); !strings.HasPrefix(got, wantPrefix) {
80 t.Errorf("Formatter.class(%v): %q should have a class prefix", st, got)
81 }
82 }
83
84 var styleBuf bytes.Buffer
85 err := withPrefix.WriteCSS(&styleBuf, styles.Fallback)
86 assert.NoError(t, err)
87 if !strings.Contains(styleBuf.String(), ".some-prefix-chroma ") {
88 t.Error("Stylesheets should have a class prefix")
89 }
90 }
91
92 func TestTableLineNumberNewlines(t *testing.T) {
93 f := New(WithClasses(true), WithLineNumbers(true), LineNumbersInTable(true))
94 it, err := lexers.Get("go").Tokenise(nil, "package main\nfunc main()\n{\nprintln(`hello world`)\n}\n")
95 assert.NoError(t, err)
96
97 var buf bytes.Buffer
98 err = f.Format(&buf, styles.Fallback, it)
99 assert.NoError(t, err)
100
101
102
103
104
105 assert.Contains(t, buf.String(), `<span class="lnt">2
106 </span><span class="lnt">3
107 </span><span class="lnt">4
108 </span>`)
109 }
110
111 func TestWrapLongLines(t *testing.T) {
112 f := New(WithClasses(false), WrapLongLines(true))
113 it, err := lexers.Get("go").Tokenise(nil, "package main\nfunc main()\n{\nprintln(\"hello world\")\n}\n")
114 assert.NoError(t, err)
115
116 var buf bytes.Buffer
117 err = f.Format(&buf, styles.Fallback, it)
118 assert.NoError(t, err)
119
120 assert.Regexp(t, `<pre.*style=".*white-space:pre-wrap;word-break:break-word;`, buf.String())
121 }
122
123 func TestHighlightLines(t *testing.T) {
124 f := New(WithClasses(true), HighlightLines([][2]int{{4, 5}}))
125 it, err := lexers.Get("go").Tokenise(nil, "package main\nfunc main()\n{\nprintln(\"hello world\")\n}\n")
126 assert.NoError(t, err)
127
128 var buf bytes.Buffer
129 err = f.Format(&buf, styles.Fallback, it)
130 assert.NoError(t, err)
131
132 assert.Contains(t, buf.String(), `<span class="line hl"><span class="cl">`)
133 }
134
135 func TestLineNumbers(t *testing.T) {
136 f := New(WithClasses(true), WithLineNumbers(true))
137 it, err := lexers.Get("bash").Tokenise(nil, "echo FOO")
138 assert.NoError(t, err)
139
140 var buf bytes.Buffer
141 err = f.Format(&buf, styles.Fallback, it)
142 assert.NoError(t, err)
143
144 assert.Contains(t, buf.String(), `<span class="line"><span class="ln">1</span><span class="cl"><span class="nb">echo</span> FOO</span></span>`)
145 }
146
147 func TestPreWrapper(t *testing.T) {
148 f := New(Standalone(true), WithClasses(true))
149 it, err := lexers.Get("bash").Tokenise(nil, "echo FOO")
150 assert.NoError(t, err)
151
152 var buf bytes.Buffer
153 err = f.Format(&buf, styles.Fallback, it)
154 assert.NoError(t, err)
155
156 assert.Regexp(t, "<body class=\"bg\">\n<pre.*class=\"chroma\"><code><span class=\"line\"><span class=\"cl\"><span class=\"nb\">echo</span> FOO</span></span></code></pre>\n</body>\n</html>", buf.String())
157 assert.Regexp(t, `\.bg { .+ }`, buf.String())
158 assert.Regexp(t, `\.chroma { .+ }`, buf.String())
159 }
160
161 func TestLinkeableLineNumbers(t *testing.T) {
162 f := New(WithClasses(true), WithLineNumbers(true), LinkableLineNumbers(true, "line"))
163 it, err := lexers.Get("go").Tokenise(nil, "package main\nfunc main()\n{\nprintln(\"hello world\")\n}\n")
164 assert.NoError(t, err)
165
166 var buf bytes.Buffer
167 err = f.Format(&buf, styles.Fallback, it)
168 assert.NoError(t, err)
169
170 assert.Contains(t, buf.String(), `id="line1"><a style="outline: none; text-decoration:none; color:inherit" href="#line1">1</a>`)
171 assert.Contains(t, buf.String(), `id="line5"><a style="outline: none; text-decoration:none; color:inherit" href="#line5">5</a>`)
172 }
173
174 func TestTableLinkeableLineNumbers(t *testing.T) {
175 f := New(WithClasses(true), WithLineNumbers(true), LineNumbersInTable(true), LinkableLineNumbers(true, "line"))
176 it, err := lexers.Get("go").Tokenise(nil, "package main\nfunc main()\n{\nprintln(`hello world`)\n}\n")
177 assert.NoError(t, err)
178
179 var buf bytes.Buffer
180 err = f.Format(&buf, styles.Fallback, it)
181 assert.NoError(t, err)
182
183 assert.Contains(t, buf.String(), `id="line1"><a style="outline: none; text-decoration:none; color:inherit" href="#line1">1</a>`)
184 assert.Contains(t, buf.String(), `id="line5"><a style="outline: none; text-decoration:none; color:inherit" href="#line5">5</a>`)
185 }
186
187 func TestTableLineNumberSpacing(t *testing.T) {
188 testCases := []struct {
189 baseLineNumber int
190 expectedBuf string
191 }{{
192 7,
193 `<span class="lnt"> 7
194 </span><span class="lnt"> 8
195 </span><span class="lnt"> 9
196 </span><span class="lnt">10
197 </span><span class="lnt">11
198 </span>`,
199 }, {
200 6,
201 `<span class="lnt"> 6
202 </span><span class="lnt"> 7
203 </span><span class="lnt"> 8
204 </span><span class="lnt"> 9
205 </span><span class="lnt">10
206 </span>`,
207 }, {
208 5,
209 `<span class="lnt">5
210 </span><span class="lnt">6
211 </span><span class="lnt">7
212 </span><span class="lnt">8
213 </span><span class="lnt">9
214 </span>`,
215 }}
216 for i, testCase := range testCases {
217 f := New(
218 WithClasses(true),
219 WithLineNumbers(true),
220 LineNumbersInTable(true),
221 BaseLineNumber(testCase.baseLineNumber),
222 )
223 it, err := lexers.Get("go").Tokenise(nil, "package main\nfunc main()\n{\nprintln(`hello world`)\n}\n")
224 assert.NoError(t, err)
225 var buf bytes.Buffer
226 err = f.Format(&buf, styles.Fallback, it)
227 assert.NoError(t, err, "Test Case %d", i)
228 assert.Contains(t, buf.String(), testCase.expectedBuf, "Test Case %d", i)
229 }
230 }
231
232 func TestWithPreWrapper(t *testing.T) {
233 wrapper := preWrapper{
234 start: func(code bool, styleAttr string) string {
235 return fmt.Sprintf("<foo%s id=\"code-%t\">", styleAttr, code)
236 },
237 end: func(code bool) string {
238 return fmt.Sprintf("</foo>")
239 },
240 }
241
242 format := func(f *Formatter) string {
243 it, err := lexers.Get("bash").Tokenise(nil, "echo FOO")
244 assert.NoError(t, err)
245
246 var buf bytes.Buffer
247 err = f.Format(&buf, styles.Fallback, it)
248 assert.NoError(t, err)
249
250 return buf.String()
251 }
252
253 t.Run("Regular", func(t *testing.T) {
254 s := format(New(WithClasses(true)))
255 assert.Equal(t, s, `<pre tabindex="0" class="chroma"><code><span class="line"><span class="cl"><span class="nb">echo</span> FOO</span></span></code></pre>`)
256 })
257
258 t.Run("PreventSurroundingPre", func(t *testing.T) {
259 s := format(New(PreventSurroundingPre(true), WithClasses(true)))
260 assert.Equal(t, s, `<span class="line"><span class="cl"><span class="nb">echo</span> FOO</span></span>`)
261 })
262
263 t.Run("Wrapper", func(t *testing.T) {
264 s := format(New(WithPreWrapper(wrapper), WithClasses(true)))
265 assert.Equal(t, s, `<foo class="chroma" id="code-true"><span class="line"><span class="cl"><span class="nb">echo</span> FOO</span></span></foo>`)
266 })
267
268 t.Run("Wrapper, LineNumbersInTable", func(t *testing.T) {
269 s := format(New(WithPreWrapper(wrapper), WithClasses(true), WithLineNumbers(true), LineNumbersInTable(true)))
270
271 assert.Equal(t, s, `<div class="chroma">
272 <table class="lntable"><tr><td class="lntd">
273 <foo class="chroma" id="code-false"><span class="lnt">1
274 </span></foo></td>
275 <td class="lntd">
276 <foo class="chroma" id="code-true"><span class="line"><span class="cl"><span class="nb">echo</span> FOO</span></span></foo></td></tr></table>
277 </div>
278 `)
279 })
280 }
281
282 func TestReconfigureOptions(t *testing.T) {
283 options := []Option{
284 WithClasses(true),
285 WithLineNumbers(true),
286 }
287
288 options = append(options, WithLineNumbers(false))
289
290 f := New(options...)
291
292 it, err := lexers.Get("bash").Tokenise(nil, "echo FOO")
293 assert.NoError(t, err)
294
295 var buf bytes.Buffer
296 err = f.Format(&buf, styles.Fallback, it)
297
298 assert.NoError(t, err)
299 assert.Equal(t, `<pre tabindex="0" class="chroma"><code><span class="line"><span class="cl"><span class="nb">echo</span> FOO</span></span></code></pre>`, buf.String())
300 }
301
302 func TestWriteCssWithAllClasses(t *testing.T) {
303 formatter := New()
304 formatter.allClasses = true
305
306 var buf bytes.Buffer
307 err := formatter.WriteCSS(&buf, styles.Fallback)
308
309 assert.NoError(t, err)
310 assert.NotContains(t, buf.String(), ".chroma . {", "Generated css doesn't contain invalid css")
311 }
312
View as plain text