github.com/linchen2chris/hugo@v0.0.0-20230307053224-cec209389705/markup/goldmark/integration_test.go (about) 1 // Copyright 2021 The Hugo Authors. All rights reserved. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // http://www.apache.org/licenses/LICENSE-2.0 7 // 8 // Unless required by applicable law or agreed to in writing, software 9 // distributed under the License is distributed on an "AS IS" BASIS, 10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 // See the License for the specific language governing permissions and 12 // limitations under the License. 13 14 package goldmark_test 15 16 import ( 17 "fmt" 18 "strings" 19 "testing" 20 21 qt "github.com/frankban/quicktest" 22 23 "github.com/gohugoio/hugo/hugolib" 24 ) 25 26 // Issue 9463 27 func TestAttributeExclusion(t *testing.T) { 28 t.Parallel() 29 30 files := ` 31 -- config.toml -- 32 [markup.goldmark.renderer] 33 unsafe = false 34 [markup.goldmark.parser.attribute] 35 block = true 36 title = true 37 -- content/p1.md -- 38 --- 39 title: "p1" 40 --- 41 ## Heading {class="a" onclick="alert('heading')"} 42 43 > Blockquote 44 {class="b" ondblclick="alert('blockquote')"} 45 46 ~~~bash {id="c" onmouseover="alert('code fence')" LINENOS=true} 47 foo 48 ~~~ 49 -- layouts/_default/single.html -- 50 {{ .Content }} 51 ` 52 53 b := hugolib.NewIntegrationTestBuilder( 54 hugolib.IntegrationTestConfig{ 55 T: t, 56 TxtarString: files, 57 NeedsOsFS: false, 58 }, 59 ).Build() 60 61 b.AssertFileContent("public/p1/index.html", ` 62 <h2 class="a" id="heading"> 63 <blockquote class="b"> 64 <div class="highlight" id="c"> 65 `) 66 } 67 68 // Issue 9511 69 func TestAttributeExclusionWithRenderHook(t *testing.T) { 70 t.Parallel() 71 72 files := ` 73 -- content/p1.md -- 74 --- 75 title: "p1" 76 --- 77 ## Heading {onclick="alert('renderhook')" data-foo="bar"} 78 -- layouts/_default/single.html -- 79 {{ .Content }} 80 -- layouts/_default/_markup/render-heading.html -- 81 <h{{ .Level }} 82 {{- range $k, $v := .Attributes -}} 83 {{- printf " %s=%q" $k $v | safeHTMLAttr -}} 84 {{- end -}} 85 >{{ .Text | safeHTML }}</h{{ .Level }}> 86 ` 87 88 b := hugolib.NewIntegrationTestBuilder( 89 hugolib.IntegrationTestConfig{ 90 T: t, 91 TxtarString: files, 92 NeedsOsFS: false, 93 }, 94 ).Build() 95 96 b.AssertFileContent("public/p1/index.html", ` 97 <h2 data-foo="bar" id="heading">Heading</h2> 98 `) 99 } 100 101 func TestAttributesDefaultRenderer(t *testing.T) { 102 t.Parallel() 103 104 files := ` 105 -- content/p1.md -- 106 --- 107 title: "p1" 108 --- 109 ## Heading Attribute Which Needs Escaping { class="a < b" } 110 -- layouts/_default/single.html -- 111 {{ .Content }} 112 ` 113 114 b := hugolib.NewIntegrationTestBuilder( 115 hugolib.IntegrationTestConfig{ 116 T: t, 117 TxtarString: files, 118 NeedsOsFS: false, 119 }, 120 ).Build() 121 122 b.AssertFileContent("public/p1/index.html", ` 123 class="a < b" 124 `) 125 } 126 127 // Issue 9558. 128 func TestAttributesHookNoEscape(t *testing.T) { 129 t.Parallel() 130 131 files := ` 132 -- content/p1.md -- 133 --- 134 title: "p1" 135 --- 136 ## Heading Attribute Which Needs Escaping { class="Smith & Wesson" } 137 -- layouts/_default/_markup/render-heading.html -- 138 plain: |{{- range $k, $v := .Attributes -}}{{ $k }}: {{ $v }}|{{ end }}| 139 safeHTML: |{{- range $k, $v := .Attributes -}}{{ $k }}: {{ $v | safeHTML }}|{{ end }}| 140 -- layouts/_default/single.html -- 141 {{ .Content }} 142 ` 143 144 b := hugolib.NewIntegrationTestBuilder( 145 hugolib.IntegrationTestConfig{ 146 T: t, 147 TxtarString: files, 148 NeedsOsFS: false, 149 }, 150 ).Build() 151 152 b.AssertFileContent("public/p1/index.html", ` 153 plain: |class: Smith & Wesson|id: heading-attribute-which-needs-escaping| 154 safeHTML: |class: Smith & Wesson|id: heading-attribute-which-needs-escaping| 155 `) 156 } 157 158 // Issue 9504 159 func TestLinkInTitle(t *testing.T) { 160 t.Parallel() 161 162 files := ` 163 -- config.toml -- 164 -- content/p1.md -- 165 --- 166 title: "p1" 167 --- 168 ## Hello [Test](https://example.com) 169 -- layouts/_default/single.html -- 170 {{ .Content }} 171 -- layouts/_default/_markup/render-heading.html -- 172 <h{{ .Level }} id="{{ .Anchor | safeURL }}"> 173 {{ .Text | safeHTML }} 174 <a class="anchor" href="#{{ .Anchor | safeURL }}">#</a> 175 </h{{ .Level }}> 176 -- layouts/_default/_markup/render-link.html -- 177 <a href="{{ .Destination | safeURL }}"{{ with .Title}} title="{{ . }}"{{ end }}>{{ .Text | safeHTML }}</a> 178 179 ` 180 181 b := hugolib.NewIntegrationTestBuilder( 182 hugolib.IntegrationTestConfig{ 183 T: t, 184 TxtarString: files, 185 NeedsOsFS: false, 186 }, 187 ).Build() 188 189 b.AssertFileContent("public/p1/index.html", 190 "<h2 id=\"hello-testhttpsexamplecom\">\n Hello <a href=\"https://example.com\">Test</a>\n\n <a class=\"anchor\" href=\"#hello-testhttpsexamplecom\">#</a>\n</h2>", 191 ) 192 } 193 194 func TestHighlight(t *testing.T) { 195 t.Parallel() 196 197 files := ` 198 -- config.toml -- 199 [markup] 200 [markup.highlight] 201 anchorLineNos = false 202 codeFences = true 203 guessSyntax = false 204 hl_Lines = '' 205 lineAnchors = '' 206 lineNoStart = 1 207 lineNos = false 208 lineNumbersInTable = true 209 noClasses = false 210 style = 'monokai' 211 tabWidth = 4 212 -- layouts/_default/single.html -- 213 {{ .Content }} 214 -- content/p1.md -- 215 --- 216 title: "p1" 217 --- 218 219 ## Code Fences 220 221 §§§bash 222 LINE1 223 §§§ 224 225 ## Code Fences No Lexer 226 227 §§§moo 228 LINE1 229 §§§ 230 231 ## Code Fences Simple Attributes 232 233 §§A§bash { .myclass id="myid" } 234 LINE1 235 §§A§ 236 237 ## Code Fences Line Numbers 238 239 §§§bash {linenos=table,hl_lines=[8,"15-17"],linenostart=199} 240 LINE1 241 LINE2 242 LINE3 243 LINE4 244 LINE5 245 LINE6 246 LINE7 247 LINE8 248 §§§ 249 250 251 252 253 ` 254 255 b := hugolib.NewIntegrationTestBuilder( 256 hugolib.IntegrationTestConfig{ 257 T: t, 258 TxtarString: files, 259 }, 260 ).Build() 261 262 b.AssertFileContent("public/p1/index.html", 263 "<div class=\"highlight\"><pre tabindex=\"0\" class=\"chroma\"><code class=\"language-bash\" data-lang=\"bash\"><span class=\"line\"><span class=\"cl\">LINE1\n</span></span></code></pre></div>", 264 "Code Fences No Lexer</h2>\n<pre tabindex=\"0\"><code class=\"language-moo\" data-lang=\"moo\">LINE1\n</code></pre>", 265 "lnt", 266 ) 267 } 268 269 func BenchmarkRenderHooks(b *testing.B) { 270 files := ` 271 -- config.toml -- 272 -- layouts/_default/_markup/render-heading.html -- 273 <h{{ .Level }} id="{{ .Anchor | safeURL }}"> 274 {{ .Text | safeHTML }} 275 <a class="anchor" href="#{{ .Anchor | safeURL }}">#</a> 276 </h{{ .Level }}> 277 -- layouts/_default/_markup/render-link.html -- 278 <a href="{{ .Destination | safeURL }}"{{ with .Title}} title="{{ . }}"{{ end }}>{{ .Text | safeHTML }}</a> 279 -- layouts/_default/single.html -- 280 {{ .Content }} 281 ` 282 283 content := ` 284 285 ## Hello1 [Test](https://example.com) 286 287 A. 288 289 ## Hello2 [Test](https://example.com) 290 291 B. 292 293 ## Hello3 [Test](https://example.com) 294 295 C. 296 297 ## Hello4 [Test](https://example.com) 298 299 D. 300 301 [Test](https://example.com) 302 303 ## Hello5 304 305 306 ` 307 308 for i := 1; i < 100; i++ { 309 files += fmt.Sprintf("\n-- content/posts/p%d.md --\n"+content, i+1) 310 } 311 312 cfg := hugolib.IntegrationTestConfig{ 313 T: b, 314 TxtarString: files, 315 } 316 builders := make([]*hugolib.IntegrationTestBuilder, b.N) 317 318 for i := range builders { 319 builders[i] = hugolib.NewIntegrationTestBuilder(cfg) 320 } 321 322 b.ResetTimer() 323 324 for i := 0; i < b.N; i++ { 325 builders[i].Build() 326 } 327 } 328 329 func BenchmarkCodeblocks(b *testing.B) { 330 filesTemplate := ` 331 -- config.toml -- 332 [markup] 333 [markup.highlight] 334 anchorLineNos = false 335 codeFences = true 336 guessSyntax = false 337 hl_Lines = '' 338 lineAnchors = '' 339 lineNoStart = 1 340 lineNos = false 341 lineNumbersInTable = true 342 noClasses = true 343 style = 'monokai' 344 tabWidth = 4 345 -- layouts/_default/single.html -- 346 {{ .Content }} 347 ` 348 349 content := ` 350 351 FENCEgo 352 package main 353 import "fmt" 354 func main() { 355 fmt.Println("hello world") 356 } 357 FENCE 358 359 FENCEunknownlexer 360 hello 361 FENCE 362 ` 363 364 content = strings.ReplaceAll(content, "FENCE", "```") 365 366 for i := 1; i < 100; i++ { 367 filesTemplate += fmt.Sprintf("\n-- content/posts/p%d.md --\n"+content, i+1) 368 } 369 370 runBenchmark := func(files string, b *testing.B) { 371 cfg := hugolib.IntegrationTestConfig{ 372 T: b, 373 TxtarString: files, 374 } 375 builders := make([]*hugolib.IntegrationTestBuilder, b.N) 376 377 for i := range builders { 378 builders[i] = hugolib.NewIntegrationTestBuilder(cfg) 379 } 380 381 b.ResetTimer() 382 383 for i := 0; i < b.N; i++ { 384 builders[i].Build() 385 } 386 } 387 388 b.Run("Default", func(b *testing.B) { 389 runBenchmark(filesTemplate, b) 390 }) 391 392 b.Run("Hook no higlight", func(b *testing.B) { 393 files := filesTemplate + ` 394 -- layouts/_default/_markup/render-codeblock.html -- 395 {{ .Inner }} 396 ` 397 398 runBenchmark(files, b) 399 }) 400 401 } 402 403 // Iisse #8959 404 func TestHookInfiniteRecursion(t *testing.T) { 405 t.Parallel() 406 407 for _, renderFunc := range []string{"markdownify", ".Page.RenderString"} { 408 t.Run(renderFunc, func(t *testing.T) { 409 410 files := ` 411 -- config.toml -- 412 -- layouts/_default/_markup/render-link.html -- 413 <a href="{{ .Destination | safeURL }}">{{ .Text | RENDERFUNC }}</a> 414 -- layouts/_default/single.html -- 415 {{ .Content }} 416 -- content/p1.md -- 417 --- 418 title: "p1" 419 --- 420 421 https://example.org 422 423 a@b.com 424 425 426 ` 427 428 files = strings.ReplaceAll(files, "RENDERFUNC", renderFunc) 429 430 b, err := hugolib.NewIntegrationTestBuilder( 431 hugolib.IntegrationTestConfig{ 432 T: t, 433 TxtarString: files, 434 }, 435 ).BuildE() 436 437 b.Assert(err, qt.IsNotNil) 438 b.Assert(err.Error(), qt.Contains, "text is already rendered, repeating it may cause infinite recursion") 439 440 }) 441 442 } 443 444 } 445 446 // Issue 9594 447 func TestQuotesInImgAltAttr(t *testing.T) { 448 t.Parallel() 449 450 files := ` 451 -- config.toml -- 452 [markup.goldmark.extensions] 453 typographer = false 454 -- content/p1.md -- 455 --- 456 title: "p1" 457 --- 458  459 -- layouts/_default/single.html -- 460 {{ .Content }} 461 ` 462 463 b := hugolib.NewIntegrationTestBuilder( 464 hugolib.IntegrationTestConfig{ 465 T: t, 466 TxtarString: files, 467 }, 468 ).Build() 469 470 b.AssertFileContent("public/p1/index.html", ` 471 <img src="b.jpg" alt=""a""> 472 `) 473 } 474 475 func TestLinkifyProtocol(t *testing.T) { 476 t.Parallel() 477 478 runTest := func(protocol string, withHook bool) *hugolib.IntegrationTestBuilder { 479 480 files := ` 481 -- config.toml -- 482 [markup.goldmark] 483 [markup.goldmark.extensions] 484 linkify = true 485 linkifyProtocol = "PROTOCOL" 486 -- content/p1.md -- 487 --- 488 title: "p1" 489 --- 490 Link no procol: www.example.org 491 Link http procol: http://www.example.org 492 Link https procol: https://www.example.org 493 494 -- layouts/_default/single.html -- 495 {{ .Content }} 496 ` 497 files = strings.ReplaceAll(files, "PROTOCOL", protocol) 498 499 if withHook { 500 files += `-- layouts/_default/_markup/render-link.html -- 501 <a href="{{ .Destination | safeURL }}">{{ .Text | safeHTML }}</a>` 502 } 503 504 return hugolib.NewIntegrationTestBuilder( 505 hugolib.IntegrationTestConfig{ 506 T: t, 507 TxtarString: files, 508 }, 509 ).Build() 510 511 } 512 513 for _, withHook := range []bool{false, true} { 514 515 b := runTest("https", withHook) 516 517 b.AssertFileContent("public/p1/index.html", 518 "Link no procol: <a href=\"https://www.example.org\">www.example.org</a>", 519 "Link http procol: <a href=\"http://www.example.org\">http://www.example.org</a>", 520 "Link https procol: <a href=\"https://www.example.org\">https://www.example.org</a></p>", 521 ) 522 523 b = runTest("http", withHook) 524 525 b.AssertFileContent("public/p1/index.html", 526 "Link no procol: <a href=\"http://www.example.org\">www.example.org</a>", 527 "Link http procol: <a href=\"http://www.example.org\">http://www.example.org</a>", 528 "Link https procol: <a href=\"https://www.example.org\">https://www.example.org</a></p>", 529 ) 530 531 b = runTest("gopher", withHook) 532 533 b.AssertFileContent("public/p1/index.html", 534 "Link no procol: <a href=\"gopher://www.example.org\">www.example.org</a>", 535 "Link http procol: <a href=\"http://www.example.org\">http://www.example.org</a>", 536 "Link https procol: <a href=\"https://www.example.org\">https://www.example.org</a></p>", 537 ) 538 539 } 540 } 541 542 func TestGoldmarkBugs(t *testing.T) { 543 t.Parallel() 544 545 files := ` 546 -- config.toml -- 547 [markup.goldmark.renderer] 548 unsafe = true 549 -- content/p1.md -- 550 --- 551 title: "p1" 552 --- 553 554 ## Issue 9650 555 556 a <!-- b --> c 557 558 ## Issue 9658 559 560 - This is a list item <!-- Comment: an innocent-looking comment --> 561 562 563 -- layouts/_default/single.html -- 564 {{ .Content }} 565 ` 566 567 b := hugolib.NewIntegrationTestBuilder( 568 hugolib.IntegrationTestConfig{ 569 T: t, 570 TxtarString: files, 571 }, 572 ).Build() 573 574 b.AssertFileContentExact("public/p1/index.html", 575 // Issue 9650 576 "<p>a <!-- b --> c</p>", 577 // Issue 9658 (crash) 578 "<li>This is a list item <!-- Comment: an innocent-looking comment --></li>", 579 ) 580 }