2017-07-19 23:51:16 -07:00
|
|
|
package html
|
|
|
|
|
|
|
|
import (
|
2018-02-17 17:28:43 -08:00
|
|
|
"bytes"
|
2019-11-18 12:31:49 +01:00
|
|
|
"fmt"
|
2017-09-19 23:04:10 +10:00
|
|
|
"io/ioutil"
|
2017-12-05 18:00:44 +01:00
|
|
|
"strings"
|
2017-07-19 23:51:16 -07:00
|
|
|
"testing"
|
|
|
|
|
2021-06-18 16:55:49 +10:00
|
|
|
"github.com/stretchr/testify/assert"
|
2018-12-31 22:44:27 +11:00
|
|
|
|
2017-09-20 13:30:46 +10:00
|
|
|
"github.com/alecthomas/chroma"
|
2017-09-19 23:04:10 +10:00
|
|
|
"github.com/alecthomas/chroma/lexers"
|
|
|
|
"github.com/alecthomas/chroma/styles"
|
2017-07-19 23:51:16 -07:00
|
|
|
)
|
|
|
|
|
|
|
|
func TestCompressStyle(t *testing.T) {
|
2017-09-18 14:19:59 +10:00
|
|
|
style := "color: #888888; background-color: #faffff"
|
2017-07-19 23:51:16 -07:00
|
|
|
actual := compressStyle(style)
|
2017-09-18 14:19:59 +10:00
|
|
|
expected := "color:#888;background-color:#faffff"
|
2017-09-19 23:04:10 +10:00
|
|
|
assert.Equal(t, expected, actual)
|
|
|
|
}
|
|
|
|
|
|
|
|
func BenchmarkHTMLFormatter(b *testing.B) {
|
|
|
|
formatter := New()
|
|
|
|
b.ResetTimer()
|
|
|
|
for i := 0; i < b.N; i++ {
|
2018-02-15 21:01:44 +11:00
|
|
|
it, err := lexers.Get("go").Tokenise(nil, "package main\nfunc main()\n{\nprintln(`hello world`)\n}\n")
|
2017-09-20 22:19:36 +10:00
|
|
|
assert.NoError(b, err)
|
|
|
|
err = formatter.Format(ioutil.Discard, styles.Fallback, it)
|
2017-09-19 23:04:10 +10:00
|
|
|
assert.NoError(b, err)
|
|
|
|
}
|
2017-07-19 23:51:16 -07:00
|
|
|
}
|
2017-09-20 13:30:46 +10:00
|
|
|
|
|
|
|
func TestSplitTokensIntoLines(t *testing.T) {
|
2018-11-03 16:22:51 -07:00
|
|
|
in := []chroma.Token{
|
2017-09-20 13:30:46 +10:00
|
|
|
{Value: "hello", Type: chroma.NameKeyword},
|
|
|
|
{Value: " world\nwhat?\n", Type: chroma.NameKeyword},
|
|
|
|
}
|
2018-11-03 16:22:51 -07:00
|
|
|
expected := [][]chroma.Token{
|
2017-09-26 22:05:44 +10:00
|
|
|
{
|
2017-09-20 13:30:46 +10:00
|
|
|
{Type: chroma.NameKeyword, Value: "hello"},
|
|
|
|
{Type: chroma.NameKeyword, Value: " world\n"},
|
|
|
|
},
|
2017-09-26 22:05:44 +10:00
|
|
|
{
|
2017-09-20 13:30:46 +10:00
|
|
|
{Type: chroma.NameKeyword, Value: "what?\n"},
|
|
|
|
},
|
|
|
|
}
|
2018-11-08 17:16:41 +11:00
|
|
|
actual := chroma.SplitTokensIntoLines(in)
|
2017-09-20 13:30:46 +10:00
|
|
|
assert.Equal(t, expected, actual)
|
|
|
|
}
|
2017-09-20 22:30:25 +10:00
|
|
|
|
2018-01-02 20:59:02 +11:00
|
|
|
func TestFormatterStyleToCSS(t *testing.T) {
|
2017-12-05 18:00:44 +01:00
|
|
|
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)
|
|
|
|
}
|
2019-11-21 09:24:48 +01:00
|
|
|
formatter := New(WithClasses(true))
|
2017-12-05 18:00:44 +01:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2018-02-17 17:28:43 -08:00
|
|
|
|
|
|
|
func TestClassPrefix(t *testing.T) {
|
|
|
|
wantPrefix := "some-prefix-"
|
2019-11-21 09:24:48 +01:00
|
|
|
withPrefix := New(WithClasses(true), ClassPrefix(wantPrefix))
|
|
|
|
noPrefix := New(WithClasses(true))
|
2018-02-17 17:28:43 -08:00
|
|
|
for st := range chroma.StandardTypes {
|
2018-02-24 13:15:48 -08:00
|
|
|
if noPrefix.class(st) == "" {
|
|
|
|
if got := withPrefix.class(st); got != "" {
|
2018-03-03 10:16:21 +11:00
|
|
|
t.Errorf("Formatter.class(%v): prefix shouldn't be added to empty classes", st)
|
2018-02-24 13:15:48 -08:00
|
|
|
}
|
|
|
|
} else if got := withPrefix.class(st); !strings.HasPrefix(got, wantPrefix) {
|
|
|
|
t.Errorf("Formatter.class(%v): %q should have a class prefix", st, got)
|
2018-02-17 17:28:43 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
var styleBuf bytes.Buffer
|
2018-12-31 22:44:27 +11:00
|
|
|
err := withPrefix.WriteCSS(&styleBuf, styles.Fallback)
|
|
|
|
assert.NoError(t, err)
|
2018-02-17 17:28:43 -08:00
|
|
|
if !strings.Contains(styleBuf.String(), ".some-prefix-chroma ") {
|
|
|
|
t.Error("Stylesheets should have a class prefix")
|
|
|
|
}
|
|
|
|
}
|
2018-02-24 17:34:34 -08:00
|
|
|
|
|
|
|
func TestTableLineNumberNewlines(t *testing.T) {
|
2019-11-21 09:24:48 +01:00
|
|
|
f := New(WithClasses(true), WithLineNumbers(true), LineNumbersInTable(true))
|
2018-02-24 17:34:34 -08:00
|
|
|
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>`)
|
|
|
|
}
|
2019-11-18 12:31:49 +01:00
|
|
|
|
2021-11-07 10:08:48 +03:30
|
|
|
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(), `<div 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(), `<div class="line"><span class="ln">1</span><span class="cl"><span class="nb">echo</span> FOO</span></div>`)
|
|
|
|
}
|
|
|
|
|
|
|
|
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\"><div class=\"line\"><span class=\"cl\"><span class=\"nb\">echo</span> FOO</span></div></pre>\n</body>\n</html>", buf.String())
|
|
|
|
assert.Regexp(t, `\.bg { .+ }`, buf.String())
|
|
|
|
assert.Regexp(t, `\.chroma { .+ }`, buf.String())
|
|
|
|
}
|
|
|
|
|
2020-11-01 13:47:03 -05:00
|
|
|
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>`)
|
|
|
|
}
|
|
|
|
|
2020-01-09 19:35:46 +01:00
|
|
|
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)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-11-18 12:31:49 +01:00
|
|
|
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) {
|
2019-11-21 09:24:48 +01:00
|
|
|
s := format(New(WithClasses(true)))
|
2021-11-07 10:08:48 +03:30
|
|
|
assert.Equal(t, s, `<pre tabindex="0" class="chroma"><div class="line"><span class="cl"><span class="nb">echo</span> FOO</span></div></pre>`)
|
2019-11-18 12:31:49 +01:00
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("PreventSurroundingPre", func(t *testing.T) {
|
2019-11-21 09:24:48 +01:00
|
|
|
s := format(New(PreventSurroundingPre(true), WithClasses(true)))
|
2021-11-07 10:08:48 +03:30
|
|
|
assert.Equal(t, s, `<div class="line"><span class="cl"><span class="nb">echo</span> FOO</span></div>`)
|
2019-11-18 12:31:49 +01:00
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("Wrapper", func(t *testing.T) {
|
2019-11-21 09:24:48 +01:00
|
|
|
s := format(New(WithPreWrapper(wrapper), WithClasses(true)))
|
2021-11-07 10:08:48 +03:30
|
|
|
assert.Equal(t, s, `<foo class="chroma" id="code-true"><div class="line"><span class="cl"><span class="nb">echo</span> FOO</span></div></foo>`)
|
2019-11-18 12:31:49 +01:00
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("Wrapper, LineNumbersInTable", func(t *testing.T) {
|
2019-11-21 09:24:48 +01:00
|
|
|
s := format(New(WithPreWrapper(wrapper), WithClasses(true), WithLineNumbers(true), LineNumbersInTable(true)))
|
2019-11-18 12:31:49 +01:00
|
|
|
|
|
|
|
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">
|
2021-11-07 10:08:48 +03:30
|
|
|
<foo class="chroma" id="code-true"><div class="line"><span class="cl"><span class="nb">echo</span> FOO</span></div></foo></td></tr></table>
|
2019-11-18 12:31:49 +01:00
|
|
|
</div>
|
|
|
|
`)
|
|
|
|
})
|
|
|
|
}
|
2019-11-21 09:24:48 +01:00
|
|
|
|
|
|
|
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)
|
2021-11-07 10:08:48 +03:30
|
|
|
assert.Equal(t, `<pre tabindex="0" class="chroma"><div class="line"><span class="cl"><span class="nb">echo</span> FOO</span></div></pre>`, buf.String())
|
2019-11-21 09:24:48 +01:00
|
|
|
}
|
2020-05-09 14:49:31 +02:00
|
|
|
|
|
|
|
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")
|
|
|
|
}
|