git.sr.ht/~pingoo/stdx@v0.0.0-20240218134121-094174641f6e/goldmark-highlighting/highlighting_test.go (about) 1 package highlighting 2 3 import ( 4 "bytes" 5 "fmt" 6 "strings" 7 "testing" 8 9 "github.com/alecthomas/chroma/v2" 10 chromahtml "github.com/alecthomas/chroma/v2/formatters/html" 11 "github.com/yuin/goldmark" 12 "github.com/yuin/goldmark/testutil" 13 "github.com/yuin/goldmark/util" 14 ) 15 16 func TestHighlighting(t *testing.T) { 17 var css bytes.Buffer 18 markdown := goldmark.New( 19 goldmark.WithExtensions( 20 NewHighlighting( 21 WithStyle("monokai"), 22 WithCSSWriter(&css), 23 WithFormatOptions( 24 chromahtml.WithClasses(true), 25 chromahtml.WithLineNumbers(false), 26 ), 27 WithWrapperRenderer(func(w util.BufWriter, c CodeBlockContext, entering bool) { 28 _, ok := c.Language() 29 if entering { 30 if !ok { 31 w.WriteString("<pre><code>") 32 return 33 } 34 w.WriteString(`<div class="highlight">`) 35 } else { 36 if !ok { 37 w.WriteString("</pre></code>") 38 return 39 } 40 w.WriteString(`</div>`) 41 } 42 }), 43 WithCodeBlockOptions(func(c CodeBlockContext) []chromahtml.Option { 44 if language, ok := c.Language(); ok { 45 // Turn on line numbers for Go only. 46 if string(language) == "go" { 47 return []chromahtml.Option{ 48 chromahtml.WithLineNumbers(true), 49 } 50 } 51 } 52 return nil 53 }), 54 ), 55 ), 56 ) 57 var buffer bytes.Buffer 58 if err := markdown.Convert([]byte(` 59 Title 60 ======= 61 `+"``` go\n"+`func main() { 62 fmt.Println("ok") 63 } 64 `+"```"+` 65 `), &buffer); err != nil { 66 t.Fatal(err) 67 } 68 69 if strings.TrimSpace(buffer.String()) != strings.TrimSpace(` 70 <h1>Title</h1> 71 <div class="highlight"><pre tabindex="0" class="chroma"><code><span class="line"><span class="ln">1</span><span class="cl"><span class="kd">func</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span> 72 </span></span><span class="line"><span class="ln">2</span><span class="cl"> <span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">"ok"</span><span class="p">)</span> 73 </span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="p">}</span> 74 </span></span></code></pre></div> 75 `) { 76 t.Error("failed to render HTML\n") 77 } 78 79 expected := strings.TrimSpace(`/* Background */ .bg { color: #f8f8f2; background-color: #272822; } 80 /* PreWrapper */ .chroma { color: #f8f8f2; background-color: #272822; } 81 /* LineNumbers targeted by URL anchor */ .chroma .ln:target { color: #f8f8f2; background-color: #3c3d38 } 82 /* LineNumbersTable targeted by URL anchor */ .chroma .lnt:target { color: #f8f8f2; background-color: #3c3d38 } 83 /* Error */ .chroma .err { color: #960050; background-color: #1e0010 } 84 /* LineTableTD */ .chroma .lntd { vertical-align: top; padding: 0; margin: 0; border: 0; } 85 /* LineTable */ .chroma .lntable { border-spacing: 0; padding: 0; margin: 0; border: 0; } 86 /* LineHighlight */ .chroma .hl { background-color: #3c3d38 } 87 /* LineNumbersTable */ .chroma .lnt { white-space: pre; user-select: none; margin-right: 0.4em; padding: 0 0.4em 0 0.4em;color: #7f7f7f } 88 /* LineNumbers */ .chroma .ln { white-space: pre; user-select: none; margin-right: 0.4em; padding: 0 0.4em 0 0.4em;color: #7f7f7f } 89 /* Line */ .chroma .line { display: flex; } 90 /* Keyword */ .chroma .k { color: #66d9ef } 91 /* KeywordConstant */ .chroma .kc { color: #66d9ef } 92 /* KeywordDeclaration */ .chroma .kd { color: #66d9ef } 93 /* KeywordNamespace */ .chroma .kn { color: #f92672 } 94 /* KeywordPseudo */ .chroma .kp { color: #66d9ef } 95 /* KeywordReserved */ .chroma .kr { color: #66d9ef } 96 /* KeywordType */ .chroma .kt { color: #66d9ef } 97 /* NameAttribute */ .chroma .na { color: #a6e22e } 98 /* NameClass */ .chroma .nc { color: #a6e22e } 99 /* NameConstant */ .chroma .no { color: #66d9ef } 100 /* NameDecorator */ .chroma .nd { color: #a6e22e } 101 /* NameException */ .chroma .ne { color: #a6e22e } 102 /* NameFunction */ .chroma .nf { color: #a6e22e } 103 /* NameOther */ .chroma .nx { color: #a6e22e } 104 /* NameTag */ .chroma .nt { color: #f92672 } 105 /* Literal */ .chroma .l { color: #ae81ff } 106 /* LiteralDate */ .chroma .ld { color: #e6db74 } 107 /* LiteralString */ .chroma .s { color: #e6db74 } 108 /* LiteralStringAffix */ .chroma .sa { color: #e6db74 } 109 /* LiteralStringBacktick */ .chroma .sb { color: #e6db74 } 110 /* LiteralStringChar */ .chroma .sc { color: #e6db74 } 111 /* LiteralStringDelimiter */ .chroma .dl { color: #e6db74 } 112 /* LiteralStringDoc */ .chroma .sd { color: #e6db74 } 113 /* LiteralStringDouble */ .chroma .s2 { color: #e6db74 } 114 /* LiteralStringEscape */ .chroma .se { color: #ae81ff } 115 /* LiteralStringHeredoc */ .chroma .sh { color: #e6db74 } 116 /* LiteralStringInterpol */ .chroma .si { color: #e6db74 } 117 /* LiteralStringOther */ .chroma .sx { color: #e6db74 } 118 /* LiteralStringRegex */ .chroma .sr { color: #e6db74 } 119 /* LiteralStringSingle */ .chroma .s1 { color: #e6db74 } 120 /* LiteralStringSymbol */ .chroma .ss { color: #e6db74 } 121 /* LiteralNumber */ .chroma .m { color: #ae81ff } 122 /* LiteralNumberBin */ .chroma .mb { color: #ae81ff } 123 /* LiteralNumberFloat */ .chroma .mf { color: #ae81ff } 124 /* LiteralNumberHex */ .chroma .mh { color: #ae81ff } 125 /* LiteralNumberInteger */ .chroma .mi { color: #ae81ff } 126 /* LiteralNumberIntegerLong */ .chroma .il { color: #ae81ff } 127 /* LiteralNumberOct */ .chroma .mo { color: #ae81ff } 128 /* Operator */ .chroma .o { color: #f92672 } 129 /* OperatorWord */ .chroma .ow { color: #f92672 } 130 /* Comment */ .chroma .c { color: #75715e } 131 /* CommentHashbang */ .chroma .ch { color: #75715e } 132 /* CommentMultiline */ .chroma .cm { color: #75715e } 133 /* CommentSingle */ .chroma .c1 { color: #75715e } 134 /* CommentSpecial */ .chroma .cs { color: #75715e } 135 /* CommentPreproc */ .chroma .cp { color: #75715e } 136 /* CommentPreprocFile */ .chroma .cpf { color: #75715e } 137 /* GenericDeleted */ .chroma .gd { color: #f92672 } 138 /* GenericEmph */ .chroma .ge { font-style: italic } 139 /* GenericInserted */ .chroma .gi { color: #a6e22e } 140 /* GenericStrong */ .chroma .gs { font-weight: bold } 141 /* GenericSubheading */ .chroma .gu { color: #75715e }`) 142 143 gotten := strings.TrimSpace(css.String()) 144 145 if expected != gotten { 146 diff := testutil.DiffPretty([]byte(expected), []byte(gotten)) 147 t.Errorf("incorrect CSS.\n%s", string(diff)) 148 } 149 } 150 151 func TestHighlighting2(t *testing.T) { 152 markdown := goldmark.New( 153 goldmark.WithExtensions( 154 Highlighting, 155 ), 156 ) 157 var buffer bytes.Buffer 158 if err := markdown.Convert([]byte(` 159 Title 160 ======= 161 `+"```"+` 162 func main() { 163 fmt.Println("ok") 164 } 165 `+"```"+` 166 `), &buffer); err != nil { 167 t.Fatal(err) 168 } 169 170 if strings.TrimSpace(buffer.String()) != strings.TrimSpace(` 171 <h1>Title</h1> 172 <pre><code>func main() { 173 fmt.Println("ok") 174 } 175 </code></pre> 176 `) { 177 t.Error("failed to render HTML") 178 } 179 } 180 181 func TestHighlighting3(t *testing.T) { 182 markdown := goldmark.New( 183 goldmark.WithExtensions( 184 Highlighting, 185 ), 186 ) 187 var buffer bytes.Buffer 188 if err := markdown.Convert([]byte(` 189 Title 190 ======= 191 192 `+"```"+`cpp {hl_lines=[1,2]} 193 #include <iostream> 194 int main() { 195 std::cout<< "hello" << std::endl; 196 } 197 `+"```"+` 198 `), &buffer); err != nil { 199 t.Fatal(err) 200 } 201 if strings.TrimSpace(buffer.String()) != strings.TrimSpace(` 202 <h1>Title</h1> 203 <pre tabindex="0" style="background-color:#fff;display:grid;"><code><span style="display:flex; background-color:#e5e5e5"><span><span style="color:#999;font-weight:bold;font-style:italic">#include</span> <span style="color:#999;font-weight:bold;font-style:italic"><iostream></span><span style="color:#999;font-weight:bold;font-style:italic"> 204 </span></span></span><span style="display:flex; background-color:#e5e5e5"><span><span style="color:#999;font-weight:bold;font-style:italic"></span><span style="color:#458;font-weight:bold">int</span> <span style="color:#900;font-weight:bold">main</span>() { 205 </span></span><span style="display:flex;"><span> std<span style="color:#000;font-weight:bold">::</span>cout<span style="color:#000;font-weight:bold"><<</span> <span style="color:#d14">"hello"</span> <span style="color:#000;font-weight:bold"><<</span> std<span style="color:#000;font-weight:bold">::</span>endl; 206 </span></span><span style="display:flex;"><span>} 207 </span></span></code></pre> 208 `) { 209 t.Error("failed to render HTML") 210 } 211 } 212 213 func TestHighlightingCustom(t *testing.T) { 214 custom := chroma.MustNewStyle("custom", chroma.StyleEntries{ 215 chroma.Background: "#cccccc bg:#1d1d1d", 216 chroma.Comment: "#999999", 217 chroma.CommentSpecial: "#cd0000", 218 chroma.Keyword: "#cc99cd", 219 chroma.KeywordDeclaration: "#cc99cd", 220 chroma.KeywordNamespace: "#cc99cd", 221 chroma.KeywordType: "#cc99cd", 222 chroma.Operator: "#67cdcc", 223 chroma.OperatorWord: "#cdcd00", 224 chroma.NameClass: "#f08d49", 225 chroma.NameBuiltin: "#f08d49", 226 chroma.NameFunction: "#f08d49", 227 chroma.NameException: "bold #666699", 228 chroma.NameVariable: "#00cdcd", 229 chroma.LiteralString: "#7ec699", 230 chroma.LiteralNumber: "#f08d49", 231 chroma.LiteralStringBoolean: "#f08d49", 232 chroma.GenericHeading: "bold #000080", 233 chroma.GenericSubheading: "bold #800080", 234 chroma.GenericDeleted: "#e2777a", 235 chroma.GenericInserted: "#cc99cd", 236 chroma.GenericError: "#e2777a", 237 chroma.GenericEmph: "italic", 238 chroma.GenericStrong: "bold", 239 chroma.GenericPrompt: "bold #000080", 240 chroma.GenericOutput: "#888", 241 chroma.GenericTraceback: "#04D", 242 chroma.GenericUnderline: "underline", 243 chroma.Error: "border:#e2777a", 244 }) 245 246 var css bytes.Buffer 247 markdown := goldmark.New( 248 goldmark.WithExtensions( 249 NewHighlighting( 250 WithStyle("monokai"), // to make sure it is overrided even if present 251 WithCustomStyle(custom), 252 WithCSSWriter(&css), 253 WithFormatOptions( 254 chromahtml.WithClasses(true), 255 chromahtml.WithLineNumbers(false), 256 ), 257 WithWrapperRenderer(func(w util.BufWriter, c CodeBlockContext, entering bool) { 258 _, ok := c.Language() 259 if entering { 260 if !ok { 261 w.WriteString("<pre><code>") 262 return 263 } 264 w.WriteString(`<div class="highlight">`) 265 } else { 266 if !ok { 267 w.WriteString("</pre></code>") 268 return 269 } 270 w.WriteString(`</div>`) 271 } 272 }), 273 WithCodeBlockOptions(func(c CodeBlockContext) []chromahtml.Option { 274 if language, ok := c.Language(); ok { 275 // Turn on line numbers for Go only. 276 if string(language) == "go" { 277 return []chromahtml.Option{ 278 chromahtml.WithLineNumbers(true), 279 } 280 } 281 } 282 return nil 283 }), 284 ), 285 ), 286 ) 287 var buffer bytes.Buffer 288 if err := markdown.Convert([]byte(` 289 Title 290 ======= 291 `+"``` go\n"+`func main() { 292 fmt.Println("ok") 293 } 294 `+"```"+` 295 `), &buffer); err != nil { 296 t.Fatal(err) 297 } 298 299 if strings.TrimSpace(buffer.String()) != strings.TrimSpace(` 300 <h1>Title</h1> 301 <div class="highlight"><pre tabindex="0" class="chroma"><code><span class="line"><span class="ln">1</span><span class="cl"><span class="kd">func</span> <span class="nf">main</span><span class="p">()</span> <span class="p">{</span> 302 </span></span><span class="line"><span class="ln">2</span><span class="cl"> <span class="nx">fmt</span><span class="p">.</span><span class="nf">Println</span><span class="p">(</span><span class="s">"ok"</span><span class="p">)</span> 303 </span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="p">}</span> 304 </span></span></code></pre></div> 305 `) { 306 t.Error("failed to render HTML", buffer.String()) 307 } 308 309 expected := strings.TrimSpace(`/* Background */ .bg { color: #cccccc; background-color: #1d1d1d; } 310 /* PreWrapper */ .chroma { color: #cccccc; background-color: #1d1d1d; } 311 /* LineNumbers targeted by URL anchor */ .chroma .ln:target { color: #cccccc; background-color: #333333 } 312 /* LineNumbersTable targeted by URL anchor */ .chroma .lnt:target { color: #cccccc; background-color: #333333 } 313 /* Error */ .chroma .err { } 314 /* LineTableTD */ .chroma .lntd { vertical-align: top; padding: 0; margin: 0; border: 0; } 315 /* LineTable */ .chroma .lntable { border-spacing: 0; padding: 0; margin: 0; border: 0; } 316 /* LineHighlight */ .chroma .hl { background-color: #333333 } 317 /* LineNumbersTable */ .chroma .lnt { white-space: pre; user-select: none; margin-right: 0.4em; padding: 0 0.4em 0 0.4em;color: #666666 } 318 /* LineNumbers */ .chroma .ln { white-space: pre; user-select: none; margin-right: 0.4em; padding: 0 0.4em 0 0.4em;color: #666666 } 319 /* Line */ .chroma .line { display: flex; } 320 /* Keyword */ .chroma .k { color: #cc99cd } 321 /* KeywordConstant */ .chroma .kc { color: #cc99cd } 322 /* KeywordDeclaration */ .chroma .kd { color: #cc99cd } 323 /* KeywordNamespace */ .chroma .kn { color: #cc99cd } 324 /* KeywordPseudo */ .chroma .kp { color: #cc99cd } 325 /* KeywordReserved */ .chroma .kr { color: #cc99cd } 326 /* KeywordType */ .chroma .kt { color: #cc99cd } 327 /* NameBuiltin */ .chroma .nb { color: #f08d49 } 328 /* NameClass */ .chroma .nc { color: #f08d49 } 329 /* NameException */ .chroma .ne { color: #666699; font-weight: bold } 330 /* NameFunction */ .chroma .nf { color: #f08d49 } 331 /* NameVariable */ .chroma .nv { color: #00cdcd } 332 /* LiteralString */ .chroma .s { color: #7ec699 } 333 /* LiteralStringAffix */ .chroma .sa { color: #7ec699 } 334 /* LiteralStringBacktick */ .chroma .sb { color: #7ec699 } 335 /* LiteralStringChar */ .chroma .sc { color: #7ec699 } 336 /* LiteralStringDelimiter */ .chroma .dl { color: #7ec699 } 337 /* LiteralStringDoc */ .chroma .sd { color: #7ec699 } 338 /* LiteralStringDouble */ .chroma .s2 { color: #7ec699 } 339 /* LiteralStringEscape */ .chroma .se { color: #7ec699 } 340 /* LiteralStringHeredoc */ .chroma .sh { color: #7ec699 } 341 /* LiteralStringInterpol */ .chroma .si { color: #7ec699 } 342 /* LiteralStringOther */ .chroma .sx { color: #7ec699 } 343 /* LiteralStringRegex */ .chroma .sr { color: #7ec699 } 344 /* LiteralStringSingle */ .chroma .s1 { color: #7ec699 } 345 /* LiteralStringSymbol */ .chroma .ss { color: #7ec699 } 346 /* LiteralNumber */ .chroma .m { color: #f08d49 } 347 /* LiteralNumberBin */ .chroma .mb { color: #f08d49 } 348 /* LiteralNumberFloat */ .chroma .mf { color: #f08d49 } 349 /* LiteralNumberHex */ .chroma .mh { color: #f08d49 } 350 /* LiteralNumberInteger */ .chroma .mi { color: #f08d49 } 351 /* LiteralNumberIntegerLong */ .chroma .il { color: #f08d49 } 352 /* LiteralNumberOct */ .chroma .mo { color: #f08d49 } 353 /* Operator */ .chroma .o { color: #67cdcc } 354 /* OperatorWord */ .chroma .ow { color: #cdcd00 } 355 /* Comment */ .chroma .c { color: #999999 } 356 /* CommentHashbang */ .chroma .ch { color: #999999 } 357 /* CommentMultiline */ .chroma .cm { color: #999999 } 358 /* CommentSingle */ .chroma .c1 { color: #999999 } 359 /* CommentSpecial */ .chroma .cs { color: #cd0000 } 360 /* CommentPreproc */ .chroma .cp { color: #999999 } 361 /* CommentPreprocFile */ .chroma .cpf { color: #999999 } 362 /* GenericDeleted */ .chroma .gd { color: #e2777a } 363 /* GenericEmph */ .chroma .ge { font-style: italic } 364 /* GenericError */ .chroma .gr { color: #e2777a } 365 /* GenericHeading */ .chroma .gh { color: #000080; font-weight: bold } 366 /* GenericInserted */ .chroma .gi { color: #cc99cd } 367 /* GenericOutput */ .chroma .go { color: #888888 } 368 /* GenericPrompt */ .chroma .gp { color: #000080; font-weight: bold } 369 /* GenericStrong */ .chroma .gs { font-weight: bold } 370 /* GenericSubheading */ .chroma .gu { color: #800080; font-weight: bold } 371 /* GenericTraceback */ .chroma .gt { color: #0044dd } 372 /* GenericUnderline */ .chroma .gl { text-decoration: underline }`) 373 374 gotten := strings.TrimSpace(css.String()) 375 376 if expected != gotten { 377 diff := testutil.DiffPretty([]byte(expected), []byte(gotten)) 378 t.Errorf("incorrect CSS.\n%s", string(diff)) 379 } 380 } 381 382 func TestHighlightingHlLines(t *testing.T) { 383 markdown := goldmark.New( 384 goldmark.WithExtensions( 385 NewHighlighting( 386 WithFormatOptions( 387 chromahtml.WithClasses(true), 388 ), 389 ), 390 ), 391 ) 392 393 for i, test := range []struct { 394 attributes string 395 expect []int 396 }{ 397 {`hl_lines=["2"]`, []int{2}}, 398 {`hl_lines=["2-3",5],linenostart=5`, []int{2, 3, 5}}, 399 {`hl_lines=["2-3"]`, []int{2, 3}}, 400 {`hl_lines=["2-3",5],linenostart="5"`, []int{2, 3}}, // linenostart must be a number. string values are ignored 401 } { 402 t.Run(fmt.Sprint(i), func(t *testing.T) { 403 var buffer bytes.Buffer 404 codeBlock := fmt.Sprintf(`bash {%s} 405 LINE1 406 LINE2 407 LINE3 408 LINE4 409 LINE5 410 LINE6 411 LINE7 412 LINE8 413 `, test.attributes) 414 415 if err := markdown.Convert([]byte(` 416 `+"```"+codeBlock+"```"+` 417 `), &buffer); err != nil { 418 t.Fatal(err) 419 } 420 421 for _, line := range test.expect { 422 expectStr := fmt.Sprintf("<span class=\"line hl\"><span class=\"cl\">LINE%d\n</span></span>", line) 423 if !strings.Contains(buffer.String(), expectStr) { 424 t.Fatal("got\n", buffer.String(), "\nexpected\n", expectStr) 425 } 426 } 427 }) 428 } 429 } 430 431 type nopPreWrapper struct{} 432 433 // Start is called to write a start <pre> element. 434 func (nopPreWrapper) Start(code bool, styleAttr string) string { return "" } 435 436 // End is called to write the end </pre> element. 437 func (nopPreWrapper) End(code bool) string { return "" } 438 439 func TestHighlightingLinenos(t *testing.T) { 440 outputLineNumbersInTable := `<div class="chroma"> 441 <table class="lntable"><tr><td class="lntd"> 442 <span class="lnt">1 443 </span></td> 444 <td class="lntd"> 445 <span class="line"><span class="cl">LINE1 446 </span></span></td></tr></table> 447 </div>` 448 449 for i, test := range []struct { 450 attributes string 451 lineNumbers bool 452 lineNumbersInTable bool 453 expect string 454 }{ 455 {`linenos=true`, false, false, `<span class="line"><span class="ln">1</span><span class="cl">LINE1 456 </span></span>`}, 457 {`linenos=false`, false, false, `<span class="line"><span class="cl">LINE1 458 </span></span>`}, 459 {``, true, false, `<span class="line"><span class="ln">1</span><span class="cl">LINE1 460 </span></span>`}, 461 {``, true, true, outputLineNumbersInTable}, 462 {`linenos=inline`, true, true, `<span class="line"><span class="ln">1</span><span class="cl">LINE1 463 </span></span>`}, 464 {`linenos=foo`, false, false, `<span class="line"><span class="ln">1</span><span class="cl">LINE1 465 </span></span>`}, 466 {`linenos=table`, false, false, outputLineNumbersInTable}, 467 } { 468 t.Run(fmt.Sprint(i), func(t *testing.T) { 469 markdown := goldmark.New( 470 goldmark.WithExtensions( 471 NewHighlighting( 472 WithFormatOptions( 473 chromahtml.WithLineNumbers(test.lineNumbers), 474 chromahtml.LineNumbersInTable(test.lineNumbersInTable), 475 chromahtml.WithPreWrapper(nopPreWrapper{}), 476 chromahtml.WithClasses(true), 477 ), 478 ), 479 ), 480 ) 481 482 var buffer bytes.Buffer 483 codeBlock := fmt.Sprintf(`bash {%s} 484 LINE1 485 `, test.attributes) 486 487 content := "```" + codeBlock + "```" 488 489 if err := markdown.Convert([]byte(content), &buffer); err != nil { 490 t.Fatal(err) 491 } 492 493 s := strings.TrimSpace(buffer.String()) 494 495 if s != test.expect { 496 t.Fatal("got\n", s, "\nexpected\n", test.expect) 497 } 498 }) 499 } 500 } 501 502 func TestHighlightingGuessLanguage(t *testing.T) { 503 markdown := goldmark.New( 504 goldmark.WithExtensions( 505 NewHighlighting( 506 WithGuessLanguage(true), 507 WithFormatOptions( 508 chromahtml.WithClasses(true), 509 chromahtml.WithLineNumbers(true), 510 ), 511 ), 512 ), 513 ) 514 var buffer bytes.Buffer 515 if err := markdown.Convert([]byte("```"+` 516 LINE 517 `+"```"), &buffer); err != nil { 518 t.Fatal(err) 519 } 520 if strings.TrimSpace(buffer.String()) != strings.TrimSpace(` 521 <pre tabindex="0" class="chroma"><code><span class="line"><span class="ln">1</span><span class="cl">LINE 522 </span></span></code></pre> 523 `) { 524 t.Errorf("render mismatch, got\n%s", buffer.String()) 525 } 526 } 527 528 func TestCoalesceNeeded(t *testing.T) { 529 markdown := goldmark.New( 530 goldmark.WithExtensions( 531 NewHighlighting( 532 // WithGuessLanguage(true), 533 WithFormatOptions( 534 chromahtml.WithClasses(true), 535 chromahtml.WithLineNumbers(true), 536 ), 537 ), 538 ), 539 ) 540 var buffer bytes.Buffer 541 if err := markdown.Convert([]byte("```http"+` 542 GET /foo HTTP/1.1 543 Content-Type: application/json 544 User-Agent: foo 545 546 { 547 "hello": "world" 548 } 549 `+"```"), &buffer); err != nil { 550 t.Fatal(err) 551 } 552 if strings.TrimSpace(buffer.String()) != strings.TrimSpace(` 553 <pre tabindex="0" class="chroma"><code><span class="line"><span class="ln">1</span><span class="cl"><span class="nf">GET</span> <span class="nn">/foo</span> <span class="kr">HTTP</span><span class="o">/</span><span class="m">1.1</span> 554 </span></span><span class="line"><span class="ln">2</span><span class="cl"><span class="n">Content-Type</span><span class="o">:</span> <span class="l">application/json</span> 555 </span></span><span class="line"><span class="ln">3</span><span class="cl"><span class="n">User-Agent</span><span class="o">:</span> <span class="l">foo</span> 556 </span></span><span class="line"><span class="ln">4</span><span class="cl"> 557 </span></span><span class="line"><span class="ln">5</span><span class="cl"><span class="p">{</span> 558 </span></span><span class="line"><span class="ln">6</span><span class="cl"> <span class="nt">"hello"</span><span class="p">:</span> <span class="s2">"world"</span> 559 </span></span><span class="line"><span class="ln">7</span><span class="cl"><span class="p">}</span> 560 </span></span></code></pre> 561 `) { 562 t.Errorf("render mismatch, got\n%s", buffer.String()) 563 } 564 }