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 
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 }