...

Source file src/github.com/alecthomas/chroma/v2/formatters/html/html_test.go

Documentation: github.com/alecthomas/chroma/v2/formatters/html

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

View as plain text