// Copyright 2021 The Hugo Authors. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package goldmark_test import ( "fmt" "strings" "testing" qt "github.com/frankban/quicktest" "github.com/gohugoio/hugo/hugolib" ) // Issue 9463 func TestAttributeExclusion(t *testing.T) { t.Parallel() files := ` -- config.toml -- [markup.goldmark.renderer] unsafe = false [markup.goldmark.parser.attribute] block = true title = true -- content/p1.md -- --- title: "p1" --- ## Heading {class="a" onclick="alert('heading')"} > Blockquote {class="b" ondblclick="alert('blockquote')"} ~~~bash {id="c" onmouseover="alert('code fence')" LINENOS=true} foo ~~~ -- layouts/_default/single.html -- {{ .Content }} ` b := hugolib.NewIntegrationTestBuilder( hugolib.IntegrationTestConfig{ T: t, TxtarString: files, NeedsOsFS: false, }, ).Build() b.AssertFileContent("public/p1/index.html", `

`) } // Issue 9511 func TestAttributeExclusionWithRenderHook(t *testing.T) { t.Parallel() files := ` -- content/p1.md -- --- title: "p1" --- ## Heading {onclick="alert('renderhook')" data-foo="bar"} -- layouts/_default/single.html -- {{ .Content }} -- layouts/_default/_markup/render-heading.html -- {{ .Text | safeHTML }} ` b := hugolib.NewIntegrationTestBuilder( hugolib.IntegrationTestConfig{ T: t, TxtarString: files, NeedsOsFS: false, }, ).Build() b.AssertFileContent("public/p1/index.html", `

Heading

`) } func TestAttributesDefaultRenderer(t *testing.T) { t.Parallel() files := ` -- content/p1.md -- --- title: "p1" --- ## Heading Attribute Which Needs Escaping { class="a < b" } -- layouts/_default/single.html -- {{ .Content }} ` b := hugolib.NewIntegrationTestBuilder( hugolib.IntegrationTestConfig{ T: t, TxtarString: files, NeedsOsFS: false, }, ).Build() b.AssertFileContent("public/p1/index.html", ` class="a < b" `) } // Issue 9558. func TestAttributesHookNoEscape(t *testing.T) { t.Parallel() files := ` -- content/p1.md -- --- title: "p1" --- ## Heading Attribute Which Needs Escaping { class="Smith & Wesson" } -- layouts/_default/_markup/render-heading.html -- plain: |{{- range $k, $v := .Attributes -}}{{ $k }}: {{ $v }}|{{ end }}| safeHTML: |{{- range $k, $v := .Attributes -}}{{ $k }}: {{ $v | safeHTML }}|{{ end }}| -- layouts/_default/single.html -- {{ .Content }} ` b := hugolib.NewIntegrationTestBuilder( hugolib.IntegrationTestConfig{ T: t, TxtarString: files, NeedsOsFS: false, }, ).Build() b.AssertFileContent("public/p1/index.html", ` plain: |class: Smith & Wesson|id: heading-attribute-which-needs-escaping| safeHTML: |class: Smith & Wesson|id: heading-attribute-which-needs-escaping| `) } // Issue 9504 func TestLinkInTitle(t *testing.T) { t.Parallel() files := ` -- config.toml -- -- content/p1.md -- --- title: "p1" --- ## Hello [Test](https://example.com) -- layouts/_default/single.html -- {{ .Content }} -- layouts/_default/_markup/render-heading.html -- {{ .Text | safeHTML }} # -- layouts/_default/_markup/render-link.html -- {{ .Text | safeHTML }} ` b := hugolib.NewIntegrationTestBuilder( hugolib.IntegrationTestConfig{ T: t, TxtarString: files, NeedsOsFS: false, }, ).Build() b.AssertFileContent("public/p1/index.html", "

\n Hello Test\n\n #\n

", ) } func TestHighlight(t *testing.T) { t.Parallel() files := ` -- config.toml -- [markup] [markup.highlight] anchorLineNos = false codeFences = true guessSyntax = false hl_Lines = '' lineAnchors = '' lineNoStart = 1 lineNos = false lineNumbersInTable = true noClasses = false style = 'monokai' tabWidth = 4 -- layouts/_default/single.html -- {{ .Content }} -- content/p1.md -- --- title: "p1" --- ## Code Fences §§§bash LINE1 §§§ ## Code Fences No Lexer §§§moo LINE1 §§§ ## Code Fences Simple Attributes §§A§bash { .myclass id="myid" } LINE1 §§A§ ## Code Fences Line Numbers §§§bash {linenos=table,hl_lines=[8,"15-17"],linenostart=199} LINE1 LINE2 LINE3 LINE4 LINE5 LINE6 LINE7 LINE8 §§§ ` b := hugolib.NewIntegrationTestBuilder( hugolib.IntegrationTestConfig{ T: t, TxtarString: files, }, ).Build() b.AssertFileContent("public/p1/index.html", "
LINE1\n
", "Code Fences No Lexer

\n
LINE1\n
", "lnt", ) } func BenchmarkRenderHooks(b *testing.B) { files := ` -- config.toml -- -- layouts/_default/_markup/render-heading.html -- {{ .Text | safeHTML }} # -- layouts/_default/_markup/render-link.html -- {{ .Text | safeHTML }} -- layouts/_default/single.html -- {{ .Content }} ` content := ` ## Hello1 [Test](https://example.com) A. ## Hello2 [Test](https://example.com) B. ## Hello3 [Test](https://example.com) C. ## Hello4 [Test](https://example.com) D. [Test](https://example.com) ## Hello5 ` for i := 1; i < 100; i++ { files += fmt.Sprintf("\n-- content/posts/p%d.md --\n"+content, i+1) } cfg := hugolib.IntegrationTestConfig{ T: b, TxtarString: files, } builders := make([]*hugolib.IntegrationTestBuilder, b.N) for i := range builders { builders[i] = hugolib.NewIntegrationTestBuilder(cfg) } b.ResetTimer() for i := 0; i < b.N; i++ { builders[i].Build() } } func BenchmarkCodeblocks(b *testing.B) { filesTemplate := ` -- config.toml -- [markup] [markup.highlight] anchorLineNos = false codeFences = true guessSyntax = false hl_Lines = '' lineAnchors = '' lineNoStart = 1 lineNos = false lineNumbersInTable = true noClasses = true style = 'monokai' tabWidth = 4 -- layouts/_default/single.html -- {{ .Content }} ` content := ` FENCEgo package main import "fmt" func main() { fmt.Println("hello world") } FENCE FENCEunknownlexer hello FENCE ` content = strings.ReplaceAll(content, "FENCE", "```") for i := 1; i < 100; i++ { filesTemplate += fmt.Sprintf("\n-- content/posts/p%d.md --\n"+content, i+1) } runBenchmark := func(files string, b *testing.B) { cfg := hugolib.IntegrationTestConfig{ T: b, TxtarString: files, } builders := make([]*hugolib.IntegrationTestBuilder, b.N) for i := range builders { builders[i] = hugolib.NewIntegrationTestBuilder(cfg) } b.ResetTimer() for i := 0; i < b.N; i++ { builders[i].Build() } } b.Run("Default", func(b *testing.B) { runBenchmark(filesTemplate, b) }) b.Run("Hook no higlight", func(b *testing.B) { files := filesTemplate + ` -- layouts/_default/_markup/render-codeblock.html -- {{ .Inner }} ` runBenchmark(files, b) }) } // Iisse #8959 func TestHookInfiniteRecursion(t *testing.T) { t.Parallel() for _, renderFunc := range []string{"markdownify", ".Page.RenderString"} { t.Run(renderFunc, func(t *testing.T) { files := ` -- config.toml -- -- layouts/_default/_markup/render-link.html -- {{ .Text | RENDERFUNC }} -- layouts/_default/single.html -- {{ .Content }} -- content/p1.md -- --- title: "p1" --- https://example.org a@b.com ` files = strings.ReplaceAll(files, "RENDERFUNC", renderFunc) b, err := hugolib.NewIntegrationTestBuilder( hugolib.IntegrationTestConfig{ T: t, TxtarString: files, }, ).BuildE() b.Assert(err, qt.IsNotNil) b.Assert(err.Error(), qt.Contains, "text is already rendered, repeating it may cause infinite recursion") }) } } // Issue 9594 func TestQuotesInImgAltAttr(t *testing.T) { t.Parallel() files := ` -- config.toml -- [markup.goldmark.extensions] typographer = false -- content/p1.md -- --- title: "p1" --- !["a"](b.jpg) -- layouts/_default/single.html -- {{ .Content }} ` b := hugolib.NewIntegrationTestBuilder( hugolib.IntegrationTestConfig{ T: t, TxtarString: files, }, ).Build() b.AssertFileContent("public/p1/index.html", ` "a" `) } func TestLinkifyProtocol(t *testing.T) { t.Parallel() runTest := func(protocol string, withHook bool) *hugolib.IntegrationTestBuilder { files := ` -- config.toml -- [markup.goldmark] [markup.goldmark.extensions] linkify = true linkifyProtocol = "PROTOCOL" -- content/p1.md -- --- title: "p1" --- Link no procol: www.example.org Link http procol: http://www.example.org Link https procol: https://www.example.org -- layouts/_default/single.html -- {{ .Content }} ` files = strings.ReplaceAll(files, "PROTOCOL", protocol) if withHook { files += `-- layouts/_default/_markup/render-link.html -- {{ .Text | safeHTML }}` } return hugolib.NewIntegrationTestBuilder( hugolib.IntegrationTestConfig{ T: t, TxtarString: files, }, ).Build() } for _, withHook := range []bool{false, true} { b := runTest("https", withHook) b.AssertFileContent("public/p1/index.html", "Link no procol: www.example.org", "Link http procol: http://www.example.org", "Link https procol: https://www.example.org

", ) b = runTest("http", withHook) b.AssertFileContent("public/p1/index.html", "Link no procol: www.example.org", "Link http procol: http://www.example.org", "Link https procol: https://www.example.org

", ) b = runTest("gopher", withHook) b.AssertFileContent("public/p1/index.html", "Link no procol: www.example.org", "Link http procol: http://www.example.org", "Link https procol: https://www.example.org

", ) } } func TestGoldmarkBugs(t *testing.T) { t.Parallel() files := ` -- config.toml -- [markup.goldmark.renderer] unsafe = true -- content/p1.md -- --- title: "p1" --- ## Issue 9650 a c ## Issue 9658 - This is a list item -- layouts/_default/single.html -- {{ .Content }} ` b := hugolib.NewIntegrationTestBuilder( hugolib.IntegrationTestConfig{ T: t, TxtarString: files, }, ).Build() b.AssertFileContentExact("public/p1/index.html", // Issue 9650 "

a c

", // Issue 9658 (crash) "
  • This is a list item
  • ", ) } // Issue #7332 // Issue #11587 func TestGoldmarkEmojiExtension(t *testing.T) { t.Parallel() files := ` -- config.toml -- enableEmoji = true -- content/p1.md -- --- title: "p1" --- ~~~text :x: ~~~ {{% include "/p2" %}} {{< sc1 >}}:smiley:{{< /sc1 >}} {{< sc2 >}}:+1:{{< /sc2 >}} {{% sc3 %}}:-1:{{% /sc3 %}} -- content/p2.md -- --- title: "p2" --- :heavy_check_mark: -- layouts/shortcodes/include.html -- {{ $p := site.GetPage (.Get 0) }} {{ $p.RenderShortcodes }} -- layouts/shortcodes/sc1.html -- sc1_begin|{{ .Inner }}|sc1_end -- layouts/shortcodes/sc2.html -- sc2_begin|{{ .Inner | .Page.RenderString }}|sc2_end -- layouts/shortcodes/sc3.html -- sc3_begin|{{ .Inner }}|sc3_end -- layouts/_default/single.html -- {{ .Content }} ` b := hugolib.NewIntegrationTestBuilder( hugolib.IntegrationTestConfig{ T: t, TxtarString: files, }, ).Build() b.AssertFileContentExact("public/p1/index.html", // Issue #7332 ":x:\n", // Issue #11587 "

    ✔️

    ", // Should not be converted to emoji "sc1_begin|:smiley:|sc1_end", // Should be converted to emoji "sc2_begin|👍|sc2_end", // Should be converted to emoji "sc3_begin|👎|sc3_end", ) } func TestEmojiDisabled(t *testing.T) { t.Parallel() files := ` -- config.toml -- enableEmoji = false -- content/p1.md -- --- title: "p1" --- :x: -- layouts/_default/single.html -- {{ .Content }} ` b := hugolib.NewIntegrationTestBuilder( hugolib.IntegrationTestConfig{ T: t, TxtarString: files, }, ).Build() b.AssertFileContentExact("public/p1/index.html", "

    :x:

    ") } func TestEmojiDefaultConfig(t *testing.T) { t.Parallel() files := ` -- content/p1.md -- --- title: "p1" --- :x: -- layouts/_default/single.html -- {{ .Content }} ` b := hugolib.NewIntegrationTestBuilder( hugolib.IntegrationTestConfig{ T: t, TxtarString: files, }, ).Build() b.AssertFileContentExact("public/p1/index.html", "

    :x:

    ") }