1
0
mirror of https://github.com/alecthomas/chroma.git synced 2025-03-19 21:10:15 +02:00
chroma/formatters/html/html_test.go
Siavash Askari Nasr d18e8a46f2 Add InlineCode option for inline code blocks
- Add an `InlineCode` option for inline code blocks
- When `PreventSurroundingPre` option is enabled, do not wrap the code by `Line` and `CodeLine`
- Write and update related tests
2022-06-14 21:17:50 +10:00

361 lines
11 KiB
Go

package html
import (
"bytes"
"fmt"
"io/ioutil"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/alecthomas/chroma/v2"
"github.com/alecthomas/chroma/v2/lexers"
"github.com/alecthomas/chroma/v2/styles"
)
func TestCompressStyle(t *testing.T) {
style := "color: #888888; background-color: #faffff"
actual := compressStyle(style)
expected := "color:#888;background-color:#faffff"
assert.Equal(t, expected, actual)
}
func BenchmarkHTMLFormatter(b *testing.B) {
formatter := New()
b.ResetTimer()
for i := 0; i < b.N; i++ {
it, err := lexers.Get("go").Tokenise(nil, "package main\nfunc main()\n{\nprintln(`hello world`)\n}\n")
assert.NoError(b, err)
err = formatter.Format(ioutil.Discard, styles.Fallback, it)
assert.NoError(b, err)
}
}
func TestSplitTokensIntoLines(t *testing.T) {
in := []chroma.Token{
{Value: "hello", Type: chroma.NameKeyword},
{Value: " world\nwhat?\n", Type: chroma.NameKeyword},
}
expected := [][]chroma.Token{
{
{Type: chroma.NameKeyword, Value: "hello"},
{Type: chroma.NameKeyword, Value: " world\n"},
},
{
{Type: chroma.NameKeyword, Value: "what?\n"},
},
}
actual := chroma.SplitTokensIntoLines(in)
assert.Equal(t, expected, actual)
}
func TestFormatterStyleToCSS(t *testing.T) {
builder := styles.Get("github").Builder()
builder.Add(chroma.LineHighlight, "bg:#ffffcc")
builder.Add(chroma.LineNumbers, "bold")
style, err := builder.Build()
if err != nil {
t.Error(err)
}
formatter := New(WithClasses(true))
css := formatter.styleToCSS(style)
for _, s := range css {
if strings.HasPrefix(strings.TrimSpace(s), ";") {
t.Errorf("rule starts with semicolon - expected valid css rule without semicolon: %v", s)
}
}
}
func TestClassPrefix(t *testing.T) {
wantPrefix := "some-prefix-"
withPrefix := New(WithClasses(true), ClassPrefix(wantPrefix))
noPrefix := New(WithClasses(true))
for st := range chroma.StandardTypes {
if noPrefix.class(st) == "" {
if got := withPrefix.class(st); got != "" {
t.Errorf("Formatter.class(%v): prefix shouldn't be added to empty classes", st)
}
} else if got := withPrefix.class(st); !strings.HasPrefix(got, wantPrefix) {
t.Errorf("Formatter.class(%v): %q should have a class prefix", st, got)
}
}
var styleBuf bytes.Buffer
err := withPrefix.WriteCSS(&styleBuf, styles.Fallback)
assert.NoError(t, err)
if !strings.Contains(styleBuf.String(), ".some-prefix-chroma ") {
t.Error("Stylesheets should have a class prefix")
}
}
func TestTableLineNumberNewlines(t *testing.T) {
f := New(WithClasses(true), WithLineNumbers(true), LineNumbersInTable(true))
it, err := lexers.Get("go").Tokenise(nil, "package main\nfunc main()\n{\nprintln(`hello world`)\n}\n")
assert.NoError(t, err)
var buf bytes.Buffer
err = f.Format(&buf, styles.Fallback, it)
assert.NoError(t, err)
// Don't bother testing the whole output, just verify it's got line numbers
// in a <pre>-friendly format.
// Note: placing the newlines inside the <span> lets browser selections look
// better, instead of "skipping" over the span margin.
assert.Contains(t, buf.String(), `<span class="lnt">2
</span><span class="lnt">3
</span><span class="lnt">4
</span>`)
}
func TestTabWidthStyle(t *testing.T) {
f := New(TabWidth(4), WithClasses(false))
it, err := lexers.Get("bash").Tokenise(nil, "echo FOO")
assert.NoError(t, err)
var buf bytes.Buffer
err = f.Format(&buf, styles.Fallback, it)
assert.NoError(t, err)
assert.Regexp(t, `<pre.*style=".*background-color:[^;]+;-moz-tab-size:4;-o-tab-size:4;tab-size:4;[^"]*".+`, buf.String())
}
func TestWithCustomCSS(t *testing.T) {
f := New(WithClasses(false), WithCustomCSS(map[chroma.TokenType]string{chroma.Line: `display: inline;`}))
it, err := lexers.Get("bash").Tokenise(nil, "echo FOO")
assert.NoError(t, err)
var buf bytes.Buffer
err = f.Format(&buf, styles.Fallback, it)
assert.NoError(t, err)
assert.Regexp(t, `<span style="display:flex;display:inline;"><span><span style=".*">echo</span> FOO</span></span>`, buf.String())
}
func TestWithCustomCSSStyleInheritance(t *testing.T) {
f := New(WithClasses(false), WithCustomCSS(map[chroma.TokenType]string{
chroma.String: `background: blue;`,
chroma.LiteralStringDouble: `color: tomato;`,
}))
it, err := lexers.Get("bash").Tokenise(nil, `echo "FOO"`)
assert.NoError(t, err)
var buf bytes.Buffer
err = f.Format(&buf, styles.Fallback, it)
assert.NoError(t, err)
assert.Regexp(t, ` <span style=".*;background:blue;color:tomato;">&#34;FOO&#34;</span>`, buf.String())
}
func TestWrapLongLines(t *testing.T) {
f := New(WithClasses(false), WrapLongLines(true))
it, err := lexers.Get("go").Tokenise(nil, "package main\nfunc main()\n{\nprintln(\"hello world\")\n}\n")
assert.NoError(t, err)
var buf bytes.Buffer
err = f.Format(&buf, styles.Fallback, it)
assert.NoError(t, err)
assert.Regexp(t, `<pre.*style=".*white-space:pre-wrap;word-break:break-word;`, buf.String())
}
func TestHighlightLines(t *testing.T) {
f := New(WithClasses(true), HighlightLines([][2]int{{4, 5}}))
it, err := lexers.Get("go").Tokenise(nil, "package main\nfunc main()\n{\nprintln(\"hello world\")\n}\n")
assert.NoError(t, err)
var buf bytes.Buffer
err = f.Format(&buf, styles.Fallback, it)
assert.NoError(t, err)
assert.Contains(t, buf.String(), `<span class="line hl"><span class="cl">`)
}
func TestLineNumbers(t *testing.T) {
f := New(WithClasses(true), WithLineNumbers(true))
it, err := lexers.Get("bash").Tokenise(nil, "echo FOO")
assert.NoError(t, err)
var buf bytes.Buffer
err = f.Format(&buf, styles.Fallback, it)
assert.NoError(t, err)
assert.Contains(t, buf.String(), `<span class="line"><span class="ln">1</span><span class="cl"><span class="nb">echo</span> FOO</span></span>`)
}
func TestPreWrapper(t *testing.T) {
f := New(Standalone(true), WithClasses(true))
it, err := lexers.Get("bash").Tokenise(nil, "echo FOO")
assert.NoError(t, err)
var buf bytes.Buffer
err = f.Format(&buf, styles.Fallback, it)
assert.NoError(t, err)
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())
assert.Regexp(t, `\.bg { .+ }`, buf.String())
assert.Regexp(t, `\.chroma { .+ }`, buf.String())
}
func TestLinkeableLineNumbers(t *testing.T) {
f := New(WithClasses(true), WithLineNumbers(true), LinkableLineNumbers(true, "line"))
it, err := lexers.Get("go").Tokenise(nil, "package main\nfunc main()\n{\nprintln(\"hello world\")\n}\n")
assert.NoError(t, err)
var buf bytes.Buffer
err = f.Format(&buf, styles.Fallback, it)
assert.NoError(t, err)
assert.Contains(t, buf.String(), `id="line1"><a style="outline: none; text-decoration:none; color:inherit" href="#line1">1</a>`)
assert.Contains(t, buf.String(), `id="line5"><a style="outline: none; text-decoration:none; color:inherit" href="#line5">5</a>`)
}
func TestTableLinkeableLineNumbers(t *testing.T) {
f := New(WithClasses(true), WithLineNumbers(true), LineNumbersInTable(true), LinkableLineNumbers(true, "line"))
it, err := lexers.Get("go").Tokenise(nil, "package main\nfunc main()\n{\nprintln(`hello world`)\n}\n")
assert.NoError(t, err)
var buf bytes.Buffer
err = f.Format(&buf, styles.Fallback, it)
assert.NoError(t, err)
assert.Contains(t, buf.String(), `id="line1"><a style="outline: none; text-decoration:none; color:inherit" href="#line1">1</a>`)
assert.Contains(t, buf.String(), `id="line5"><a style="outline: none; text-decoration:none; color:inherit" href="#line5">5</a>`)
}
func TestTableLineNumberSpacing(t *testing.T) {
testCases := []struct {
baseLineNumber int
expectedBuf string
}{{
7,
`<span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span><span class="lnt">11
</span>`,
}, {
6,
`<span class="lnt"> 6
</span><span class="lnt"> 7
</span><span class="lnt"> 8
</span><span class="lnt"> 9
</span><span class="lnt">10
</span>`,
}, {
5,
`<span class="lnt">5
</span><span class="lnt">6
</span><span class="lnt">7
</span><span class="lnt">8
</span><span class="lnt">9
</span>`,
}}
for i, testCase := range testCases {
f := New(
WithClasses(true),
WithLineNumbers(true),
LineNumbersInTable(true),
BaseLineNumber(testCase.baseLineNumber),
)
it, err := lexers.Get("go").Tokenise(nil, "package main\nfunc main()\n{\nprintln(`hello world`)\n}\n")
assert.NoError(t, err)
var buf bytes.Buffer
err = f.Format(&buf, styles.Fallback, it)
assert.NoError(t, err, "Test Case %d", i)
assert.Contains(t, buf.String(), testCase.expectedBuf, "Test Case %d", i)
}
}
func TestWithPreWrapper(t *testing.T) {
wrapper := preWrapper{
start: func(code bool, styleAttr string) string {
return fmt.Sprintf("<foo%s id=\"code-%t\">", styleAttr, code)
},
end: func(code bool) string {
return fmt.Sprintf("</foo>")
},
}
format := func(f *Formatter) string {
it, err := lexers.Get("bash").Tokenise(nil, "echo FOO")
assert.NoError(t, err)
var buf bytes.Buffer
err = f.Format(&buf, styles.Fallback, it)
assert.NoError(t, err)
return buf.String()
}
t.Run("Regular", func(t *testing.T) {
s := format(New(WithClasses(true)))
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>`)
})
t.Run("PreventSurroundingPre", func(t *testing.T) {
s := format(New(PreventSurroundingPre(true), WithClasses(true)))
assert.Equal(t, s, `<span class="nb">echo</span> FOO`)
})
t.Run("InlineCode", func(t *testing.T) {
s := format(New(InlineCode(true), WithClasses(true)))
assert.Equal(t, s, `<code class="chroma"><span class="nb">echo</span> FOO</code>`)
})
t.Run("InlineCode, inline styles", func(t *testing.T) {
s := format(New(InlineCode(true)))
assert.Regexp(t, `<code style=".+?"><span style=".+?">echo</span> FOO</code>`, s)
})
t.Run("Wrapper", func(t *testing.T) {
s := format(New(WithPreWrapper(wrapper), WithClasses(true)))
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>`)
})
t.Run("Wrapper, LineNumbersInTable", func(t *testing.T) {
s := format(New(WithPreWrapper(wrapper), WithClasses(true), WithLineNumbers(true), LineNumbersInTable(true)))
assert.Equal(t, s, `<div class="chroma">
<table class="lntable"><tr><td class="lntd">
<foo class="chroma" id="code-false"><span class="lnt">1
</span></foo></td>
<td class="lntd">
<foo class="chroma" id="code-true"><span class="line"><span class="cl"><span class="nb">echo</span> FOO</span></span></foo></td></tr></table>
</div>
`)
})
}
func TestReconfigureOptions(t *testing.T) {
options := []Option{
WithClasses(true),
WithLineNumbers(true),
}
options = append(options, WithLineNumbers(false))
f := New(options...)
it, err := lexers.Get("bash").Tokenise(nil, "echo FOO")
assert.NoError(t, err)
var buf bytes.Buffer
err = f.Format(&buf, styles.Fallback, it)
assert.NoError(t, err)
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())
}
func TestWriteCssWithAllClasses(t *testing.T) {
formatter := New()
formatter.allClasses = true
var buf bytes.Buffer
err := formatter.WriteCSS(&buf, styles.Fallback)
assert.NoError(t, err)
assert.NotContains(t, buf.String(), ".chroma . {", "Generated css doesn't contain invalid css")
}