integration_test.go (12036B)
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 files := ` 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 FENCEbash 360 #!/bin/bash 361 # Usage: Hello World Bash Shell Script Using Variables 362 # Author: Vivek Gite 363 # ------------------------------------------------- 364 365 # Define bash shell variable called var 366 # Avoid spaces around the assignment operator (=) 367 var="Hello World" 368 369 # print it 370 echo "$var" 371 372 # Another way of printing it 373 printf "%s\n" "$var" 374 FENCE 375 ` 376 377 content = strings.ReplaceAll(content, "FENCE", "```") 378 379 for i := 1; i < 100; i++ { 380 files += fmt.Sprintf("\n-- content/posts/p%d.md --\n"+content, i+1) 381 } 382 383 cfg := hugolib.IntegrationTestConfig{ 384 T: b, 385 TxtarString: files, 386 } 387 builders := make([]*hugolib.IntegrationTestBuilder, b.N) 388 389 for i := range builders { 390 builders[i] = hugolib.NewIntegrationTestBuilder(cfg) 391 } 392 393 b.ResetTimer() 394 395 for i := 0; i < b.N; i++ { 396 builders[i].Build() 397 } 398 } 399 400 // Iisse #8959 401 func TestHookInfiniteRecursion(t *testing.T) { 402 t.Parallel() 403 404 for _, renderFunc := range []string{"markdownify", ".Page.RenderString"} { 405 t.Run(renderFunc, func(t *testing.T) { 406 407 files := ` 408 -- config.toml -- 409 -- layouts/_default/_markup/render-link.html -- 410 <a href="{{ .Destination | safeURL }}">{{ .Text | RENDERFUNC }}</a> 411 -- layouts/_default/single.html -- 412 {{ .Content }} 413 -- content/p1.md -- 414 --- 415 title: "p1" 416 --- 417 418 https://example.org 419 420 a@b.com 421 422 423 ` 424 425 files = strings.ReplaceAll(files, "RENDERFUNC", renderFunc) 426 427 b, err := hugolib.NewIntegrationTestBuilder( 428 hugolib.IntegrationTestConfig{ 429 T: t, 430 TxtarString: files, 431 }, 432 ).BuildE() 433 434 b.Assert(err, qt.IsNotNil) 435 b.Assert(err.Error(), qt.Contains, "text is already rendered, repeating it may cause infinite recursion") 436 437 }) 438 439 } 440 441 } 442 443 // Issue 9594 444 func TestQuotesInImgAltAttr(t *testing.T) { 445 t.Parallel() 446 447 files := ` 448 -- config.toml -- 449 [markup.goldmark.extensions] 450 typographer = false 451 -- content/p1.md -- 452 --- 453 title: "p1" 454 --- 455 !["a"](b.jpg) 456 -- layouts/_default/single.html -- 457 {{ .Content }} 458 ` 459 460 b := hugolib.NewIntegrationTestBuilder( 461 hugolib.IntegrationTestConfig{ 462 T: t, 463 TxtarString: files, 464 }, 465 ).Build() 466 467 b.AssertFileContent("public/p1/index.html", ` 468 <img src="b.jpg" alt=""a""> 469 `) 470 } 471 472 func TestLinkifyProtocol(t *testing.T) { 473 t.Parallel() 474 475 runTest := func(protocol string, withHook bool) *hugolib.IntegrationTestBuilder { 476 477 files := ` 478 -- config.toml -- 479 [markup.goldmark] 480 [markup.goldmark.extensions] 481 linkify = true 482 linkifyProtocol = "PROTOCOL" 483 -- content/p1.md -- 484 --- 485 title: "p1" 486 --- 487 Link no procol: www.example.org 488 Link http procol: http://www.example.org 489 Link https procol: https://www.example.org 490 491 -- layouts/_default/single.html -- 492 {{ .Content }} 493 ` 494 files = strings.ReplaceAll(files, "PROTOCOL", protocol) 495 496 if withHook { 497 files += `-- layouts/_default/_markup/render-link.html -- 498 <a href="{{ .Destination | safeURL }}">{{ .Text | safeHTML }}</a>` 499 } 500 501 return hugolib.NewIntegrationTestBuilder( 502 hugolib.IntegrationTestConfig{ 503 T: t, 504 TxtarString: files, 505 }, 506 ).Build() 507 508 } 509 510 for _, withHook := range []bool{false, true} { 511 512 b := runTest("https", withHook) 513 514 b.AssertFileContent("public/p1/index.html", 515 "Link no procol: <a href=\"https://www.example.org\">www.example.org</a>", 516 "Link http procol: <a href=\"http://www.example.org\">http://www.example.org</a>", 517 "Link https procol: <a href=\"https://www.example.org\">https://www.example.org</a></p>", 518 ) 519 520 b = runTest("http", withHook) 521 522 b.AssertFileContent("public/p1/index.html", 523 "Link no procol: <a href=\"http://www.example.org\">www.example.org</a>", 524 "Link http procol: <a href=\"http://www.example.org\">http://www.example.org</a>", 525 "Link https procol: <a href=\"https://www.example.org\">https://www.example.org</a></p>", 526 ) 527 528 b = runTest("gopher", withHook) 529 530 b.AssertFileContent("public/p1/index.html", 531 "Link no procol: <a href=\"gopher://www.example.org\">www.example.org</a>", 532 "Link http procol: <a href=\"http://www.example.org\">http://www.example.org</a>", 533 "Link https procol: <a href=\"https://www.example.org\">https://www.example.org</a></p>", 534 ) 535 536 } 537 } 538 539 func TestGoldmarkBugs(t *testing.T) { 540 t.Parallel() 541 542 files := ` 543 -- config.toml -- 544 [markup.goldmark.renderer] 545 unsafe = true 546 -- content/p1.md -- 547 --- 548 title: "p1" 549 --- 550 551 ## Issue 9650 552 553 a <!-- b --> c 554 555 ## Issue 9658 556 557 - This is a list item <!-- Comment: an innocent-looking comment --> 558 559 560 -- layouts/_default/single.html -- 561 {{ .Content }} 562 ` 563 564 b := hugolib.NewIntegrationTestBuilder( 565 hugolib.IntegrationTestConfig{ 566 T: t, 567 TxtarString: files, 568 }, 569 ).Build() 570 571 b.AssertFileContentExact("public/p1/index.html", 572 // Issue 9650 573 "<p>a <!-- b --> c</p>", 574 // Issue 9658 (crash) 575 "<li>This is a list item <!-- Comment: an innocent-looking comment --></li>", 576 ) 577 }