github.com/graemephi/kahugo@v0.62.3-0.20211121071557-d78c0423784d/hugolib/shortcode_test.go (about) 1 // Copyright 2019 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 hugolib 15 16 import ( 17 "fmt" 18 "path/filepath" 19 "reflect" 20 "strings" 21 "testing" 22 23 "github.com/gohugoio/hugo/config" 24 "github.com/gohugoio/hugo/markup/asciidocext" 25 "github.com/gohugoio/hugo/markup/rst" 26 27 "github.com/gohugoio/hugo/parser/pageparser" 28 "github.com/gohugoio/hugo/resources/page" 29 30 "github.com/gohugoio/hugo/deps" 31 "github.com/gohugoio/hugo/tpl" 32 "github.com/spf13/cast" 33 34 qt "github.com/frankban/quicktest" 35 ) 36 37 func CheckShortCodeMatch(t *testing.T, input, expected string, withTemplate func(templ tpl.TemplateManager) error) { 38 t.Helper() 39 CheckShortCodeMatchAndError(t, input, expected, withTemplate, false) 40 } 41 42 func CheckShortCodeMatchAndError(t *testing.T, input, expected string, withTemplate func(templ tpl.TemplateManager) error, expectError bool) { 43 t.Helper() 44 cfg, fs := newTestCfg() 45 46 cfg.Set("markup", map[string]interface{}{ 47 "defaultMarkdownHandler": "blackfriday", // TODO(bep) 48 }) 49 50 c := qt.New(t) 51 52 // Need some front matter, see https://github.com/gohugoio/hugo/issues/2337 53 contentFile := `--- 54 title: "Title" 55 --- 56 ` + input 57 58 writeSource(t, fs, "content/simple.md", contentFile) 59 60 b := newTestSitesBuilderFromDepsCfg(t, deps.DepsCfg{Fs: fs, Cfg: cfg, WithTemplate: withTemplate}).WithNothingAdded() 61 err := b.BuildE(BuildCfg{}) 62 63 if err != nil && !expectError { 64 t.Fatalf("Shortcode rendered error %s.", err) 65 } 66 67 if expectError { 68 c.Assert(err, qt.ErrorMatches, expected) 69 return 70 } 71 72 h := b.H 73 c.Assert(len(h.Sites), qt.Equals, 1) 74 75 c.Assert(len(h.Sites[0].RegularPages()), qt.Equals, 1) 76 77 output := strings.TrimSpace(content(h.Sites[0].RegularPages()[0])) 78 output = strings.TrimPrefix(output, "<p>") 79 output = strings.TrimSuffix(output, "</p>") 80 81 expected = strings.TrimSpace(expected) 82 83 if output != expected { 84 t.Fatalf("Shortcode render didn't match. got \n%q but expected \n%q", output, expected) 85 } 86 } 87 88 func TestNonSC(t *testing.T) { 89 t.Parallel() 90 // notice the syntax diff from 0.12, now comment delims must be added 91 CheckShortCodeMatch(t, "{{%/* movie 47238zzb */%}}", "{{% movie 47238zzb %}}", nil) 92 } 93 94 // Issue #929 95 func TestHyphenatedSC(t *testing.T) { 96 t.Parallel() 97 wt := func(tem tpl.TemplateManager) error { 98 tem.AddTemplate("_internal/shortcodes/hyphenated-video.html", `Playing Video {{ .Get 0 }}`) 99 return nil 100 } 101 102 CheckShortCodeMatch(t, "{{< hyphenated-video 47238zzb >}}", "Playing Video 47238zzb", wt) 103 } 104 105 // Issue #1753 106 func TestNoTrailingNewline(t *testing.T) { 107 t.Parallel() 108 wt := func(tem tpl.TemplateManager) error { 109 tem.AddTemplate("_internal/shortcodes/a.html", `{{ .Get 0 }}`) 110 return nil 111 } 112 113 CheckShortCodeMatch(t, "ab{{< a c >}}d", "abcd", wt) 114 } 115 116 func TestPositionalParamSC(t *testing.T) { 117 t.Parallel() 118 wt := func(tem tpl.TemplateManager) error { 119 tem.AddTemplate("_internal/shortcodes/video.html", `Playing Video {{ .Get 0 }}`) 120 return nil 121 } 122 123 CheckShortCodeMatch(t, "{{< video 47238zzb >}}", "Playing Video 47238zzb", wt) 124 CheckShortCodeMatch(t, "{{< video 47238zzb 132 >}}", "Playing Video 47238zzb", wt) 125 CheckShortCodeMatch(t, "{{<video 47238zzb>}}", "Playing Video 47238zzb", wt) 126 CheckShortCodeMatch(t, "{{<video 47238zzb >}}", "Playing Video 47238zzb", wt) 127 CheckShortCodeMatch(t, "{{< video 47238zzb >}}", "Playing Video 47238zzb", wt) 128 } 129 130 func TestPositionalParamIndexOutOfBounds(t *testing.T) { 131 t.Parallel() 132 wt := func(tem tpl.TemplateManager) error { 133 tem.AddTemplate("_internal/shortcodes/video.html", `Playing Video {{ with .Get 1 }}{{ . }}{{ else }}Missing{{ end }}`) 134 return nil 135 } 136 CheckShortCodeMatch(t, "{{< video 47238zzb >}}", "Playing Video Missing", wt) 137 } 138 139 // #5071 140 func TestShortcodeRelated(t *testing.T) { 141 t.Parallel() 142 wt := func(tem tpl.TemplateManager) error { 143 tem.AddTemplate("_internal/shortcodes/a.html", `{{ len (.Site.RegularPages.Related .Page) }}`) 144 return nil 145 } 146 147 CheckShortCodeMatch(t, "{{< a >}}", "0", wt) 148 } 149 150 func TestShortcodeInnerMarkup(t *testing.T) { 151 t.Parallel() 152 wt := func(tem tpl.TemplateManager) error { 153 tem.AddTemplate("shortcodes/a.html", `<div>{{ .Inner }}</div>`) 154 tem.AddTemplate("shortcodes/b.html", `**Bold**: <div>{{ .Inner }}</div>`) 155 return nil 156 } 157 158 CheckShortCodeMatch(t, 159 "{{< a >}}B: <div>{{% b %}}**Bold**{{% /b %}}</div>{{< /a >}}", 160 // This assertion looks odd, but is correct: for inner shortcodes with 161 // the {{% we treats the .Inner content as markup, but not the shortcode 162 // itself. 163 "<div>B: <div>**Bold**: <div><strong>Bold</strong></div></div></div>", 164 wt) 165 166 CheckShortCodeMatch(t, 167 "{{% b %}}This is **B**: {{< b >}}This is B{{< /b>}}{{% /b %}}", 168 "<strong>Bold</strong>: <div>This is <strong>B</strong>: <strong>Bold</strong>: <div>This is B</div></div>", 169 wt) 170 } 171 172 // some repro issues for panics in Go Fuzz testing 173 174 func TestNamedParamSC(t *testing.T) { 175 t.Parallel() 176 wt := func(tem tpl.TemplateManager) error { 177 tem.AddTemplate("_internal/shortcodes/img.html", `<img{{ with .Get "src" }} src="{{.}}"{{end}}{{with .Get "class"}} class="{{.}}"{{end}}>`) 178 return nil 179 } 180 CheckShortCodeMatch(t, `{{< img src="one" >}}`, `<img src="one">`, wt) 181 CheckShortCodeMatch(t, `{{< img class="aspen" >}}`, `<img class="aspen">`, wt) 182 CheckShortCodeMatch(t, `{{< img src= "one" >}}`, `<img src="one">`, wt) 183 CheckShortCodeMatch(t, `{{< img src ="one" >}}`, `<img src="one">`, wt) 184 CheckShortCodeMatch(t, `{{< img src = "one" >}}`, `<img src="one">`, wt) 185 CheckShortCodeMatch(t, `{{< img src = "one" class = "aspen grove" >}}`, `<img src="one" class="aspen grove">`, wt) 186 } 187 188 // Issue #2294 189 func TestNestedNamedMissingParam(t *testing.T) { 190 t.Parallel() 191 wt := func(tem tpl.TemplateManager) error { 192 tem.AddTemplate("_internal/shortcodes/acc.html", `<div class="acc">{{ .Inner }}</div>`) 193 tem.AddTemplate("_internal/shortcodes/div.html", `<div {{with .Get "class"}} class="{{ . }}"{{ end }}>{{ .Inner }}</div>`) 194 tem.AddTemplate("_internal/shortcodes/div2.html", `<div {{with .Get 0}} class="{{ . }}"{{ end }}>{{ .Inner }}</div>`) 195 return nil 196 } 197 CheckShortCodeMatch(t, 198 `{{% acc %}}{{% div %}}d1{{% /div %}}{{% div2 %}}d2{{% /div2 %}}{{% /acc %}}`, 199 "<div class=\"acc\"><div >d1</div><div >d2</div></div>", wt) 200 } 201 202 func TestIsNamedParamsSC(t *testing.T) { 203 t.Parallel() 204 wt := func(tem tpl.TemplateManager) error { 205 tem.AddTemplate("_internal/shortcodes/bynameorposition.html", `{{ with .Get "id" }}Named: {{ . }}{{ else }}Pos: {{ .Get 0 }}{{ end }}`) 206 tem.AddTemplate("_internal/shortcodes/ifnamedparams.html", `<div id="{{ if .IsNamedParams }}{{ .Get "id" }}{{ else }}{{ .Get 0 }}{{end}}">`) 207 return nil 208 } 209 CheckShortCodeMatch(t, `{{< ifnamedparams id="name" >}}`, `<div id="name">`, wt) 210 CheckShortCodeMatch(t, `{{< ifnamedparams position >}}`, `<div id="position">`, wt) 211 CheckShortCodeMatch(t, `{{< bynameorposition id="name" >}}`, `Named: name`, wt) 212 CheckShortCodeMatch(t, `{{< bynameorposition position >}}`, `Pos: position`, wt) 213 } 214 215 func TestInnerSC(t *testing.T) { 216 t.Parallel() 217 wt := func(tem tpl.TemplateManager) error { 218 tem.AddTemplate("_internal/shortcodes/inside.html", `<div{{with .Get "class"}} class="{{.}}"{{end}}>{{ .Inner }}</div>`) 219 return nil 220 } 221 CheckShortCodeMatch(t, `{{< inside class="aspen" >}}`, `<div class="aspen"></div>`, wt) 222 CheckShortCodeMatch(t, `{{< inside class="aspen" >}}More Here{{< /inside >}}`, "<div class=\"aspen\">More Here</div>", wt) 223 CheckShortCodeMatch(t, `{{< inside >}}More Here{{< /inside >}}`, "<div>More Here</div>", wt) 224 } 225 226 func TestInnerSCWithMarkdown(t *testing.T) { 227 t.Parallel() 228 wt := func(tem tpl.TemplateManager) error { 229 // Note: In Hugo 0.55 we made it so any outer {{%'s inner content was rendered as part of the surrounding 230 // markup. This solved lots of problems, but it also meant that this test had to be adjusted. 231 tem.AddTemplate("_internal/shortcodes/wrapper.html", `<div{{with .Get "class"}} class="{{.}}"{{end}}>{{ .Inner }}</div>`) 232 tem.AddTemplate("_internal/shortcodes/inside.html", `{{ .Inner }}`) 233 return nil 234 } 235 CheckShortCodeMatch(t, `{{< wrapper >}}{{% inside %}} 236 # More Here 237 238 [link](http://spf13.com) and text 239 240 {{% /inside %}}{{< /wrapper >}}`, "<div><h1 id=\"more-here\">More Here</h1>\n\n<p><a href=\"http://spf13.com\">link</a> and text</p>\n</div>", wt) 241 } 242 243 func TestEmbeddedSC(t *testing.T) { 244 t.Parallel() 245 CheckShortCodeMatch(t, `{{% figure src="/found/here" class="bananas orange" %}}`, "<figure class=\"bananas orange\"><img src=\"/found/here\"/>\n</figure>", nil) 246 CheckShortCodeMatch(t, `{{% figure src="/found/here" class="bananas orange" caption="This is a caption" %}}`, "<figure class=\"bananas orange\"><img src=\"/found/here\"\n alt=\"This is a caption\"/><figcaption>\n <p>This is a caption</p>\n </figcaption>\n</figure>", nil) 247 } 248 249 func TestNestedSC(t *testing.T) { 250 t.Parallel() 251 wt := func(tem tpl.TemplateManager) error { 252 tem.AddTemplate("_internal/shortcodes/scn1.html", `<div>Outer, inner is {{ .Inner }}</div>`) 253 tem.AddTemplate("_internal/shortcodes/scn2.html", `<div>SC2</div>`) 254 return nil 255 } 256 CheckShortCodeMatch(t, `{{% scn1 %}}{{% scn2 %}}{{% /scn1 %}}`, "<div>Outer, inner is <div>SC2</div></div>", wt) 257 258 CheckShortCodeMatch(t, `{{< scn1 >}}{{% scn2 %}}{{< /scn1 >}}`, "<div>Outer, inner is <div>SC2</div></div>", wt) 259 } 260 261 func TestNestedComplexSC(t *testing.T) { 262 t.Parallel() 263 wt := func(tem tpl.TemplateManager) error { 264 tem.AddTemplate("_internal/shortcodes/row.html", `-row-{{ .Inner}}-rowStop-`) 265 tem.AddTemplate("_internal/shortcodes/column.html", `-col-{{.Inner }}-colStop-`) 266 tem.AddTemplate("_internal/shortcodes/aside.html", `-aside-{{ .Inner }}-asideStop-`) 267 return nil 268 } 269 CheckShortCodeMatch(t, `{{< row >}}1-s{{% column %}}2-**s**{{< aside >}}3-**s**{{< /aside >}}4-s{{% /column %}}5-s{{< /row >}}6-s`, 270 "-row-1-s-col-2-<strong>s</strong>-aside-3-<strong>s</strong>-asideStop-4-s-colStop-5-s-rowStop-6-s", wt) 271 272 // turn around the markup flag 273 CheckShortCodeMatch(t, `{{% row %}}1-s{{< column >}}2-**s**{{% aside %}}3-**s**{{% /aside %}}4-s{{< /column >}}5-s{{% /row %}}6-s`, 274 "-row-1-s-col-2-<strong>s</strong>-aside-3-<strong>s</strong>-asideStop-4-s-colStop-5-s-rowStop-6-s", wt) 275 } 276 277 func TestParentShortcode(t *testing.T) { 278 t.Parallel() 279 wt := func(tem tpl.TemplateManager) error { 280 tem.AddTemplate("_internal/shortcodes/r1.html", `1: {{ .Get "pr1" }} {{ .Inner }}`) 281 tem.AddTemplate("_internal/shortcodes/r2.html", `2: {{ .Parent.Get "pr1" }}{{ .Get "pr2" }} {{ .Inner }}`) 282 tem.AddTemplate("_internal/shortcodes/r3.html", `3: {{ .Parent.Parent.Get "pr1" }}{{ .Parent.Get "pr2" }}{{ .Get "pr3" }} {{ .Inner }}`) 283 return nil 284 } 285 CheckShortCodeMatch(t, `{{< r1 pr1="p1" >}}1: {{< r2 pr2="p2" >}}2: {{< r3 pr3="p3" >}}{{< /r3 >}}{{< /r2 >}}{{< /r1 >}}`, 286 "1: p1 1: 2: p1p2 2: 3: p1p2p3 ", wt) 287 } 288 289 func TestFigureOnlySrc(t *testing.T) { 290 t.Parallel() 291 CheckShortCodeMatch(t, `{{< figure src="/found/here" >}}`, "<figure><img src=\"/found/here\"/>\n</figure>", nil) 292 } 293 294 func TestFigureCaptionAttrWithMarkdown(t *testing.T) { 295 t.Parallel() 296 CheckShortCodeMatch(t, `{{< figure src="/found/here" caption="Something **bold** _italic_" >}}`, "<figure><img src=\"/found/here\"\n alt=\"Something bold italic\"/><figcaption>\n <p>Something <strong>bold</strong> <em>italic</em></p>\n </figcaption>\n</figure>", nil) 297 CheckShortCodeMatch(t, `{{< figure src="/found/here" attr="Something **bold** _italic_" >}}`, "<figure><img src=\"/found/here\"/><figcaption>\n <p>Something <strong>bold</strong> <em>italic</em></p>\n </figcaption>\n</figure>", nil) 298 } 299 300 func TestFigureImgWidth(t *testing.T) { 301 t.Parallel() 302 CheckShortCodeMatch(t, `{{% figure src="/found/here" class="bananas orange" alt="apple" width="100px" %}}`, "<figure class=\"bananas orange\"><img src=\"/found/here\"\n alt=\"apple\" width=\"100px\"/>\n</figure>", nil) 303 } 304 305 func TestFigureImgHeight(t *testing.T) { 306 t.Parallel() 307 CheckShortCodeMatch(t, `{{% figure src="/found/here" class="bananas orange" alt="apple" height="100px" %}}`, "<figure class=\"bananas orange\"><img src=\"/found/here\"\n alt=\"apple\" height=\"100px\"/>\n</figure>", nil) 308 } 309 310 func TestFigureImgWidthAndHeight(t *testing.T) { 311 t.Parallel() 312 CheckShortCodeMatch(t, `{{% figure src="/found/here" class="bananas orange" alt="apple" width="50" height="100" %}}`, "<figure class=\"bananas orange\"><img src=\"/found/here\"\n alt=\"apple\" width=\"50\" height=\"100\"/>\n</figure>", nil) 313 } 314 315 func TestFigureLinkNoTarget(t *testing.T) { 316 t.Parallel() 317 CheckShortCodeMatch(t, `{{< figure src="/found/here" link="/jump/here/on/clicking" >}}`, "<figure><a href=\"/jump/here/on/clicking\"><img src=\"/found/here\"/></a>\n</figure>", nil) 318 } 319 320 func TestFigureLinkWithTarget(t *testing.T) { 321 t.Parallel() 322 CheckShortCodeMatch(t, `{{< figure src="/found/here" link="/jump/here/on/clicking" target="_self" >}}`, "<figure><a href=\"/jump/here/on/clicking\" target=\"_self\"><img src=\"/found/here\"/></a>\n</figure>", nil) 323 } 324 325 func TestFigureLinkWithTargetAndRel(t *testing.T) { 326 t.Parallel() 327 CheckShortCodeMatch(t, `{{< figure src="/found/here" link="/jump/here/on/clicking" target="_blank" rel="noopener" >}}`, "<figure><a href=\"/jump/here/on/clicking\" target=\"_blank\" rel=\"noopener\"><img src=\"/found/here\"/></a>\n</figure>", nil) 328 } 329 330 // #1642 331 func TestShortcodeWrappedInPIssue(t *testing.T) { 332 t.Parallel() 333 wt := func(tem tpl.TemplateManager) error { 334 tem.AddTemplate("_internal/shortcodes/bug.html", `xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx`) 335 return nil 336 } 337 CheckShortCodeMatch(t, ` 338 {{< bug >}} 339 340 {{< bug >}} 341 `, "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n\nxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", wt) 342 } 343 344 // #6866 345 func TestShortcodeIncomplete(t *testing.T) { 346 t.Parallel() 347 CheckShortCodeMatchAndError(t, `{{< >}}`, ".*shortcode has no name.*", nil, true) 348 } 349 350 func TestExtractShortcodes(t *testing.T) { 351 b := newTestSitesBuilder(t).WithSimpleConfigFile() 352 353 b.WithTemplates( 354 "default/single.html", `EMPTY`, 355 "_internal/shortcodes/tag.html", `tag`, 356 "_internal/shortcodes/legacytag.html", `{{ $_hugo_config := "{ \"version\": 1 }" }}tag`, 357 "_internal/shortcodes/sc1.html", `sc1`, 358 "_internal/shortcodes/sc2.html", `sc2`, 359 "_internal/shortcodes/inner.html", `{{with .Inner }}{{ . }}{{ end }}`, 360 "_internal/shortcodes/inner2.html", `{{.Inner}}`, 361 "_internal/shortcodes/inner3.html", `{{.Inner}}`, 362 ).WithContent("page.md", `--- 363 title: "Shortcodes Galore!" 364 --- 365 `) 366 367 b.CreateSites().Build(BuildCfg{}) 368 369 s := b.H.Sites[0] 370 371 /*errCheck := func(s string) func(name string, assert *require.Assertions, shortcode *shortcode, err error) { 372 return func(name string, assert *require.Assertions, shortcode *shortcode, err error) { 373 c.Assert(err, name, qt.Not(qt.IsNil)) 374 c.Assert(err.Error(), name, qt.Equals, s) 375 } 376 }*/ 377 378 // Make it more regexp friendly 379 strReplacer := strings.NewReplacer("[", "{", "]", "}") 380 381 str := func(s *shortcode) string { 382 if s == nil { 383 return "<nil>" 384 } 385 386 var version int 387 if s.info != nil { 388 version = s.info.ParseInfo().Config.Version 389 } 390 return strReplacer.Replace(fmt.Sprintf("%s;inline:%t;closing:%t;inner:%v;params:%v;ordinal:%d;markup:%t;version:%d;pos:%d", 391 s.name, s.isInline, s.isClosing, s.inner, s.params, s.ordinal, s.doMarkup, version, s.pos)) 392 } 393 394 regexpCheck := func(re string) func(c *qt.C, shortcode *shortcode, err error) { 395 return func(c *qt.C, shortcode *shortcode, err error) { 396 c.Assert(err, qt.IsNil) 397 c.Assert(str(shortcode), qt.Matches, ".*"+re+".*") 398 } 399 } 400 401 for _, test := range []struct { 402 name string 403 input string 404 check func(c *qt.C, shortcode *shortcode, err error) 405 }{ 406 {"one shortcode, no markup", "{{< tag >}}", regexpCheck("tag.*closing:false.*markup:false")}, 407 {"one shortcode, markup", "{{% tag %}}", regexpCheck("tag.*closing:false.*markup:true;version:2")}, 408 {"one shortcode, markup, legacy", "{{% legacytag %}}", regexpCheck("tag.*closing:false.*markup:true;version:1")}, 409 {"outer shortcode markup", "{{% inner %}}{{< tag >}}{{% /inner %}}", regexpCheck("inner.*closing:true.*markup:true")}, 410 {"inner shortcode markup", "{{< inner >}}{{% tag %}}{{< /inner >}}", regexpCheck("inner.*closing:true.*;markup:false;version:2")}, 411 {"one pos param", "{{% tag param1 %}}", regexpCheck("tag.*params:{param1}")}, 412 {"two pos params", "{{< tag param1 param2>}}", regexpCheck("tag.*params:{param1 param2}")}, 413 {"one named param", `{{% tag param1="value" %}}`, regexpCheck("tag.*params:map{param1:value}")}, 414 {"two named params", `{{< tag param1="value1" param2="value2" >}}`, regexpCheck("tag.*params:map{param\\d:value\\d param\\d:value\\d}")}, 415 {"inner", `{{< inner >}}Inner Content{{< / inner >}}`, regexpCheck("inner;inline:false;closing:true;inner:{Inner Content};")}, 416 // issue #934 417 {"inner self-closing", `{{< inner />}}`, regexpCheck("inner;.*inner:{}")}, 418 { 419 "nested inner", `{{< inner >}}Inner Content->{{% inner2 param1 %}}inner2txt{{% /inner2 %}}Inner close->{{< / inner >}}`, 420 regexpCheck("inner;.*inner:{Inner Content->.*Inner close->}"), 421 }, 422 { 423 "nested, nested inner", `{{< inner >}}inner2->{{% inner2 param1 %}}inner2txt->inner3{{< inner3>}}inner3txt{{</ inner3 >}}{{% /inner2 %}}final close->{{< / inner >}}`, 424 regexpCheck("inner:{inner2-> inner2.*{{inner2txt->inner3.*final close->}"), 425 }, 426 {"closed without content", `{{< inner param1 >}}{{< / inner >}}`, regexpCheck("inner.*inner:{}")}, 427 {"inline", `{{< my.inline >}}Hi{{< /my.inline >}}`, regexpCheck("my.inline;inline:true;closing:true;inner:{Hi};")}, 428 } { 429 430 test := test 431 432 t.Run(test.name, func(t *testing.T) { 433 t.Parallel() 434 c := qt.New(t) 435 436 counter := 0 437 placeholderFunc := func() string { 438 counter++ 439 return fmt.Sprintf("HAHA%s-%dHBHB", shortcodePlaceholderPrefix, counter) 440 } 441 442 p, err := pageparser.ParseMain(strings.NewReader(test.input), pageparser.Config{}) 443 c.Assert(err, qt.IsNil) 444 handler := newShortcodeHandler(nil, s, placeholderFunc) 445 iter := p.Iterator() 446 447 short, err := handler.extractShortcode(0, 0, iter) 448 449 test.check(c, short, err) 450 }) 451 } 452 } 453 454 func TestShortcodesInSite(t *testing.T) { 455 baseURL := "http://foo/bar" 456 457 tests := []struct { 458 contentPath string 459 content string 460 outFile string 461 expected interface{} 462 }{ 463 { 464 "sect/doc1.md", `a{{< b >}}c`, 465 filepath.FromSlash("public/sect/doc1/index.html"), "<p>abc</p>\n", 466 }, 467 // Issue #1642: Multiple shortcodes wrapped in P 468 // Deliberately forced to pass even if they maybe shouldn't. 469 { 470 "sect/doc2.md", `a 471 472 {{< b >}} 473 {{< c >}} 474 {{< d >}} 475 476 e`, 477 filepath.FromSlash("public/sect/doc2/index.html"), 478 "<p>a</p>\n\n<p>b<br />\nc\nd</p>\n\n<p>e</p>\n", 479 }, 480 { 481 "sect/doc3.md", `a 482 483 {{< b >}} 484 {{< c >}} 485 486 {{< d >}} 487 488 e`, 489 filepath.FromSlash("public/sect/doc3/index.html"), 490 "<p>a</p>\n\n<p>b<br />\nc</p>\n\nd\n\n<p>e</p>\n", 491 }, 492 { 493 "sect/doc4.md", `a 494 {{< b >}} 495 {{< b >}} 496 {{< b >}} 497 {{< b >}} 498 {{< b >}} 499 500 501 502 503 504 505 506 507 508 509 `, 510 filepath.FromSlash("public/sect/doc4/index.html"), 511 "<p>a\nb\nb\nb\nb\nb</p>\n", 512 }, 513 // #2192 #2209: Shortcodes in markdown headers 514 { 515 "sect/doc5.md", `# {{< b >}} 516 ## {{% c %}}`, 517 filepath.FromSlash("public/sect/doc5/index.html"), `-hbhb">b</h1>`, 518 }, 519 // #2223 pygments 520 { 521 "sect/doc6.md", "\n```bash\nb = {{< b >}} c = {{% c %}}\n```\n", 522 filepath.FromSlash("public/sect/doc6/index.html"), 523 `<span class="nv">b</span>`, 524 }, 525 // #2249 526 { 527 "sect/doc7.ad", `_Shortcodes:_ *b: {{< b >}} c: {{% c %}}*`, 528 filepath.FromSlash("public/sect/doc7/index.html"), 529 "<div class=\"paragraph\">\n<p><em>Shortcodes:</em> <strong>b: b c: c</strong></p>\n</div>\n", 530 }, 531 { 532 "sect/doc8.rst", `**Shortcodes:** *b: {{< b >}} c: {{% c %}}*`, 533 filepath.FromSlash("public/sect/doc8/index.html"), 534 "<div class=\"document\">\n\n\n<p><strong>Shortcodes:</strong> <em>b: b c: c</em></p>\n</div>", 535 }, 536 { 537 "sect/doc9.mmark", ` 538 --- 539 menu: 540 main: 541 parent: 'parent' 542 --- 543 **Shortcodes:** *b: {{< b >}} c: {{% c %}}*`, 544 filepath.FromSlash("public/sect/doc9/index.html"), 545 "<p><strong>Shortcodes:</strong> <em>b: b c: c</em></p>\n", 546 }, 547 // Issue #1229: Menus not available in shortcode. 548 { 549 "sect/doc10.md", `--- 550 menu: 551 main: 552 identifier: 'parent' 553 tags: 554 - Menu 555 --- 556 **Menus:** {{< menu >}}`, 557 filepath.FromSlash("public/sect/doc10/index.html"), 558 "<p><strong>Menus:</strong> 1</p>\n", 559 }, 560 // Issue #2323: Taxonomies not available in shortcode. 561 { 562 "sect/doc11.md", `--- 563 tags: 564 - Bugs 565 --- 566 **Tags:** {{< tags >}}`, 567 filepath.FromSlash("public/sect/doc11/index.html"), 568 "<p><strong>Tags:</strong> 2</p>\n", 569 }, 570 { 571 "sect/doc12.md", `--- 572 title: "Foo" 573 --- 574 575 {{% html-indented-v1 %}}`, 576 "public/sect/doc12/index.html", 577 "<h1>Hugo!</h1>", 578 }, 579 } 580 581 temp := tests[:0] 582 for _, test := range tests { 583 if strings.HasSuffix(test.contentPath, ".ad") && !asciidocext.Supports() { 584 t.Log("Skip Asciidoc test case as no Asciidoc present.") 585 continue 586 } else if strings.HasSuffix(test.contentPath, ".rst") && !rst.Supports() { 587 t.Log("Skip Rst test case as no rst2html present.") 588 continue 589 } 590 temp = append(temp, test) 591 } 592 tests = temp 593 594 sources := make([][2]string, len(tests)) 595 596 for i, test := range tests { 597 sources[i] = [2]string{filepath.FromSlash(test.contentPath), test.content} 598 } 599 600 addTemplates := func(templ tpl.TemplateManager) error { 601 templ.AddTemplate("_default/single.html", "{{.Content}} Word Count: {{ .WordCount }}") 602 603 templ.AddTemplate("_internal/shortcodes/b.html", `b`) 604 templ.AddTemplate("_internal/shortcodes/c.html", `c`) 605 templ.AddTemplate("_internal/shortcodes/d.html", `d`) 606 templ.AddTemplate("_internal/shortcodes/html-indented-v1.html", "{{ $_hugo_config := `{ \"version\": 1 }` }}"+` 607 <h1>Hugo!</h1> 608 `) 609 templ.AddTemplate("_internal/shortcodes/menu.html", `{{ len (index .Page.Menus "main").Children }}`) 610 templ.AddTemplate("_internal/shortcodes/tags.html", `{{ len .Page.Site.Taxonomies.tags }}`) 611 612 return nil 613 } 614 615 cfg, fs := newTestCfg() 616 617 cfg.Set("defaultContentLanguage", "en") 618 cfg.Set("baseURL", baseURL) 619 cfg.Set("uglyURLs", false) 620 cfg.Set("verbose", true) 621 622 cfg.Set("markup.highlight.noClasses", false) 623 cfg.Set("markup.highlight.codeFences", true) 624 cfg.Set("markup", map[string]interface{}{ 625 "defaultMarkdownHandler": "blackfriday", // TODO(bep) 626 }) 627 628 writeSourcesToSource(t, "content", fs, sources...) 629 630 s := buildSingleSite(t, deps.DepsCfg{WithTemplate: addTemplates, Fs: fs, Cfg: cfg}, BuildCfg{}) 631 632 for i, test := range tests { 633 test := test 634 t.Run(fmt.Sprintf("test=%d;contentPath=%s", i, test.contentPath), func(t *testing.T) { 635 t.Parallel() 636 637 th := newTestHelper(s.Cfg, s.Fs, t) 638 639 expected := cast.ToStringSlice(test.expected) 640 641 th.assertFileContent(filepath.FromSlash(test.outFile), expected...) 642 }) 643 644 } 645 } 646 647 func TestShortcodeMultipleOutputFormats(t *testing.T) { 648 t.Parallel() 649 650 siteConfig := ` 651 baseURL = "http://example.com/blog" 652 653 paginate = 1 654 655 disableKinds = ["section", "term", "taxonomy", "RSS", "sitemap", "robotsTXT", "404"] 656 657 [outputs] 658 home = [ "HTML", "AMP", "Calendar" ] 659 page = [ "HTML", "AMP", "JSON" ] 660 661 ` 662 663 pageTemplate := `--- 664 title: "%s" 665 --- 666 # Doc 667 668 {{< myShort >}} 669 {{< noExt >}} 670 {{%% onlyHTML %%}} 671 672 {{< myInner >}}{{< myShort >}}{{< /myInner >}} 673 674 ` 675 676 pageTemplateCSVOnly := `--- 677 title: "%s" 678 outputs: ["CSV"] 679 --- 680 # Doc 681 682 CSV: {{< myShort >}} 683 ` 684 685 b := newTestSitesBuilder(t).WithConfigFile("toml", siteConfig) 686 b.WithTemplates( 687 "layouts/_default/single.html", `Single HTML: {{ .Title }}|{{ .Content }}`, 688 "layouts/_default/single.json", `Single JSON: {{ .Title }}|{{ .Content }}`, 689 "layouts/_default/single.csv", `Single CSV: {{ .Title }}|{{ .Content }}`, 690 "layouts/index.html", `Home HTML: {{ .Title }}|{{ .Content }}`, 691 "layouts/index.amp.html", `Home AMP: {{ .Title }}|{{ .Content }}`, 692 "layouts/index.ics", `Home Calendar: {{ .Title }}|{{ .Content }}`, 693 "layouts/shortcodes/myShort.html", `ShortHTML`, 694 "layouts/shortcodes/myShort.amp.html", `ShortAMP`, 695 "layouts/shortcodes/myShort.csv", `ShortCSV`, 696 "layouts/shortcodes/myShort.ics", `ShortCalendar`, 697 "layouts/shortcodes/myShort.json", `ShortJSON`, 698 "layouts/shortcodes/noExt", `ShortNoExt`, 699 "layouts/shortcodes/onlyHTML.html", `ShortOnlyHTML`, 700 "layouts/shortcodes/myInner.html", `myInner:--{{- .Inner -}}--`, 701 ) 702 703 b.WithContent("_index.md", fmt.Sprintf(pageTemplate, "Home"), 704 "sect/mypage.md", fmt.Sprintf(pageTemplate, "Single"), 705 "sect/mycsvpage.md", fmt.Sprintf(pageTemplateCSVOnly, "Single CSV"), 706 ) 707 708 b.Build(BuildCfg{}) 709 h := b.H 710 b.Assert(len(h.Sites), qt.Equals, 1) 711 712 s := h.Sites[0] 713 home := s.getPage(page.KindHome) 714 b.Assert(home, qt.Not(qt.IsNil)) 715 b.Assert(len(home.OutputFormats()), qt.Equals, 3) 716 717 b.AssertFileContent("public/index.html", 718 "Home HTML", 719 "ShortHTML", 720 "ShortNoExt", 721 "ShortOnlyHTML", 722 "myInner:--ShortHTML--", 723 ) 724 725 b.AssertFileContent("public/amp/index.html", 726 "Home AMP", 727 "ShortAMP", 728 "ShortNoExt", 729 "ShortOnlyHTML", 730 "myInner:--ShortAMP--", 731 ) 732 733 b.AssertFileContent("public/index.ics", 734 "Home Calendar", 735 "ShortCalendar", 736 "ShortNoExt", 737 "ShortOnlyHTML", 738 "myInner:--ShortCalendar--", 739 ) 740 741 b.AssertFileContent("public/sect/mypage/index.html", 742 "Single HTML", 743 "ShortHTML", 744 "ShortNoExt", 745 "ShortOnlyHTML", 746 "myInner:--ShortHTML--", 747 ) 748 749 b.AssertFileContent("public/sect/mypage/index.json", 750 "Single JSON", 751 "ShortJSON", 752 "ShortNoExt", 753 "ShortOnlyHTML", 754 "myInner:--ShortJSON--", 755 ) 756 757 b.AssertFileContent("public/amp/sect/mypage/index.html", 758 // No special AMP template 759 "Single HTML", 760 "ShortAMP", 761 "ShortNoExt", 762 "ShortOnlyHTML", 763 "myInner:--ShortAMP--", 764 ) 765 766 b.AssertFileContent("public/sect/mycsvpage/index.csv", 767 "Single CSV", 768 "ShortCSV", 769 ) 770 } 771 772 func BenchmarkReplaceShortcodeTokens(b *testing.B) { 773 type input struct { 774 in []byte 775 replacements map[string]string 776 expect []byte 777 } 778 779 data := []struct { 780 input string 781 replacements map[string]string 782 expect []byte 783 }{ 784 {"Hello HAHAHUGOSHORTCODE-1HBHB.", map[string]string{"HAHAHUGOSHORTCODE-1HBHB": "World"}, []byte("Hello World.")}, 785 {strings.Repeat("A", 100) + " HAHAHUGOSHORTCODE-1HBHB.", map[string]string{"HAHAHUGOSHORTCODE-1HBHB": "Hello World"}, []byte(strings.Repeat("A", 100) + " Hello World.")}, 786 {strings.Repeat("A", 500) + " HAHAHUGOSHORTCODE-1HBHB.", map[string]string{"HAHAHUGOSHORTCODE-1HBHB": "Hello World"}, []byte(strings.Repeat("A", 500) + " Hello World.")}, 787 {strings.Repeat("ABCD ", 500) + " HAHAHUGOSHORTCODE-1HBHB.", map[string]string{"HAHAHUGOSHORTCODE-1HBHB": "Hello World"}, []byte(strings.Repeat("ABCD ", 500) + " Hello World.")}, 788 {strings.Repeat("A ", 3000) + " HAHAHUGOSHORTCODE-1HBHB." + strings.Repeat("BC ", 1000) + " HAHAHUGOSHORTCODE-1HBHB.", map[string]string{"HAHAHUGOSHORTCODE-1HBHB": "Hello World"}, []byte(strings.Repeat("A ", 3000) + " Hello World." + strings.Repeat("BC ", 1000) + " Hello World.")}, 789 } 790 791 in := make([]input, b.N*len(data)) 792 cnt := 0 793 for i := 0; i < b.N; i++ { 794 for _, this := range data { 795 in[cnt] = input{[]byte(this.input), this.replacements, this.expect} 796 cnt++ 797 } 798 } 799 800 b.ResetTimer() 801 cnt = 0 802 for i := 0; i < b.N; i++ { 803 for j := range data { 804 currIn := in[cnt] 805 cnt++ 806 results, err := replaceShortcodeTokens(currIn.in, currIn.replacements) 807 if err != nil { 808 b.Fatalf("[%d] failed: %s", i, err) 809 continue 810 } 811 if len(results) != len(currIn.expect) { 812 b.Fatalf("[%d] replaceShortcodeTokens, got \n%q but expected \n%q", j, results, currIn.expect) 813 } 814 815 } 816 } 817 } 818 819 func TestReplaceShortcodeTokens(t *testing.T) { 820 t.Parallel() 821 for i, this := range []struct { 822 input string 823 prefix string 824 replacements map[string]string 825 expect interface{} 826 }{ 827 {"Hello HAHAHUGOSHORTCODE-1HBHB.", "PREFIX", map[string]string{"HAHAHUGOSHORTCODE-1HBHB": "World"}, "Hello World."}, 828 {"Hello HAHAHUGOSHORTCODE-1@}@.", "PREFIX", map[string]string{"HAHAHUGOSHORTCODE-1HBHB": "World"}, false}, 829 {"HAHAHUGOSHORTCODE2-1HBHB", "PREFIX2", map[string]string{"HAHAHUGOSHORTCODE2-1HBHB": "World"}, "World"}, 830 {"Hello World!", "PREFIX2", map[string]string{}, "Hello World!"}, 831 {"!HAHAHUGOSHORTCODE-1HBHB", "PREFIX", map[string]string{"HAHAHUGOSHORTCODE-1HBHB": "World"}, "!World"}, 832 {"HAHAHUGOSHORTCODE-1HBHB!", "PREFIX", map[string]string{"HAHAHUGOSHORTCODE-1HBHB": "World"}, "World!"}, 833 {"!HAHAHUGOSHORTCODE-1HBHB!", "PREFIX", map[string]string{"HAHAHUGOSHORTCODE-1HBHB": "World"}, "!World!"}, 834 {"_{_PREFIX-1HBHB", "PREFIX", map[string]string{"HAHAHUGOSHORTCODE-1HBHB": "World"}, "_{_PREFIX-1HBHB"}, 835 {"Hello HAHAHUGOSHORTCODE-1HBHB.", "PREFIX", map[string]string{"HAHAHUGOSHORTCODE-1HBHB": "To You My Old Friend Who Told Me This Fantastic Story"}, "Hello To You My Old Friend Who Told Me This Fantastic Story."}, 836 {"A HAHAHUGOSHORTCODE-1HBHB asdf HAHAHUGOSHORTCODE-2HBHB.", "A", map[string]string{"HAHAHUGOSHORTCODE-1HBHB": "v1", "HAHAHUGOSHORTCODE-2HBHB": "v2"}, "A v1 asdf v2."}, 837 {"Hello HAHAHUGOSHORTCODE2-1HBHB. Go HAHAHUGOSHORTCODE2-2HBHB, Go, Go HAHAHUGOSHORTCODE2-3HBHB Go Go!.", "PREFIX2", map[string]string{"HAHAHUGOSHORTCODE2-1HBHB": "Europe", "HAHAHUGOSHORTCODE2-2HBHB": "Jonny", "HAHAHUGOSHORTCODE2-3HBHB": "Johnny"}, "Hello Europe. Go Jonny, Go, Go Johnny Go Go!."}, 838 {"A HAHAHUGOSHORTCODE-2HBHB HAHAHUGOSHORTCODE-1HBHB.", "PREFIX", map[string]string{"HAHAHUGOSHORTCODE-1HBHB": "A", "HAHAHUGOSHORTCODE-2HBHB": "B"}, "A B A."}, 839 {"A HAHAHUGOSHORTCODE-1HBHB HAHAHUGOSHORTCODE-2", "PREFIX", map[string]string{"HAHAHUGOSHORTCODE-1HBHB": "A"}, false}, 840 {"A HAHAHUGOSHORTCODE-1HBHB but not the second.", "PREFIX", map[string]string{"HAHAHUGOSHORTCODE-1HBHB": "A", "HAHAHUGOSHORTCODE-2HBHB": "B"}, "A A but not the second."}, 841 {"An HAHAHUGOSHORTCODE-1HBHB.", "PREFIX", map[string]string{"HAHAHUGOSHORTCODE-1HBHB": "A", "HAHAHUGOSHORTCODE-2HBHB": "B"}, "An A."}, 842 {"An HAHAHUGOSHORTCODE-1HBHB HAHAHUGOSHORTCODE-2HBHB.", "PREFIX", map[string]string{"HAHAHUGOSHORTCODE-1HBHB": "A", "HAHAHUGOSHORTCODE-2HBHB": "B"}, "An A B."}, 843 {"A HAHAHUGOSHORTCODE-1HBHB HAHAHUGOSHORTCODE-2HBHB HAHAHUGOSHORTCODE-3HBHB HAHAHUGOSHORTCODE-1HBHB HAHAHUGOSHORTCODE-3HBHB.", "PREFIX", map[string]string{"HAHAHUGOSHORTCODE-1HBHB": "A", "HAHAHUGOSHORTCODE-2HBHB": "B", "HAHAHUGOSHORTCODE-3HBHB": "C"}, "A A B C A C."}, 844 {"A HAHAHUGOSHORTCODE-1HBHB HAHAHUGOSHORTCODE-2HBHB HAHAHUGOSHORTCODE-3HBHB HAHAHUGOSHORTCODE-1HBHB HAHAHUGOSHORTCODE-3HBHB.", "PREFIX", map[string]string{"HAHAHUGOSHORTCODE-1HBHB": "A", "HAHAHUGOSHORTCODE-2HBHB": "B", "HAHAHUGOSHORTCODE-3HBHB": "C"}, "A A B C A C."}, 845 // Issue #1148 remove p-tags 10 => 846 {"Hello <p>HAHAHUGOSHORTCODE-1HBHB</p>. END.", "PREFIX", map[string]string{"HAHAHUGOSHORTCODE-1HBHB": "World"}, "Hello World. END."}, 847 {"Hello <p>HAHAHUGOSHORTCODE-1HBHB</p>. <p>HAHAHUGOSHORTCODE-2HBHB</p> END.", "PREFIX", map[string]string{"HAHAHUGOSHORTCODE-1HBHB": "World", "HAHAHUGOSHORTCODE-2HBHB": "THE"}, "Hello World. THE END."}, 848 {"Hello <p>HAHAHUGOSHORTCODE-1HBHB. END</p>.", "PREFIX", map[string]string{"HAHAHUGOSHORTCODE-1HBHB": "World"}, "Hello <p>World. END</p>."}, 849 {"<p>Hello HAHAHUGOSHORTCODE-1HBHB</p>. END.", "PREFIX", map[string]string{"HAHAHUGOSHORTCODE-1HBHB": "World"}, "<p>Hello World</p>. END."}, 850 {"Hello <p>HAHAHUGOSHORTCODE-1HBHB12", "PREFIX", map[string]string{"HAHAHUGOSHORTCODE-1HBHB": "World"}, "Hello <p>World12"}, 851 { 852 "Hello HAHAHUGOSHORTCODE-1HBHB. HAHAHUGOSHORTCODE-1HBHB-HAHAHUGOSHORTCODE-1HBHB HAHAHUGOSHORTCODE-1HBHB HAHAHUGOSHORTCODE-1HBHB HAHAHUGOSHORTCODE-1HBHB END", "P", 853 map[string]string{"HAHAHUGOSHORTCODE-1HBHB": strings.Repeat("BC", 100)}, 854 fmt.Sprintf("Hello %s. %s-%s %s %s %s END", 855 strings.Repeat("BC", 100), strings.Repeat("BC", 100), strings.Repeat("BC", 100), strings.Repeat("BC", 100), strings.Repeat("BC", 100), strings.Repeat("BC", 100)), 856 }, 857 } { 858 859 results, err := replaceShortcodeTokens([]byte(this.input), this.replacements) 860 861 if b, ok := this.expect.(bool); ok && !b { 862 if err == nil { 863 t.Errorf("[%d] replaceShortcodeTokens didn't return an expected error", i) 864 } 865 } else { 866 if err != nil { 867 t.Errorf("[%d] failed: %s", i, err) 868 continue 869 } 870 if !reflect.DeepEqual(results, []byte(this.expect.(string))) { 871 t.Errorf("[%d] replaceShortcodeTokens, got \n%q but expected \n%q", i, results, this.expect) 872 } 873 } 874 875 } 876 } 877 878 func TestShortcodeGetContent(t *testing.T) { 879 t.Parallel() 880 881 contentShortcode := ` 882 {{- $t := .Get 0 -}} 883 {{- $p := .Get 1 -}} 884 {{- $k := .Get 2 -}} 885 {{- $page := $.Page.Site.GetPage "page" $p -}} 886 {{ if $page }} 887 {{- if eq $t "bundle" -}} 888 {{- .Scratch.Set "p" ($page.Resources.GetMatch (printf "%s*" $k)) -}} 889 {{- else -}} 890 {{- $.Scratch.Set "p" $page -}} 891 {{- end -}}P1:{{ .Page.Content }}|P2:{{ $p := ($.Scratch.Get "p") }}{{ $p.Title }}/{{ $p.Content }}| 892 {{- else -}} 893 {{- errorf "Page %s is nil" $p -}} 894 {{- end -}} 895 ` 896 897 var templates []string 898 var content []string 899 900 contentWithShortcodeTemplate := `--- 901 title: doc%s 902 weight: %d 903 --- 904 Logo:{{< c "bundle" "b1" "logo.png" >}}:P1: {{< c "page" "section1/p1" "" >}}:BP1:{{< c "bundle" "b1" "bp1" >}}` 905 906 simpleContentTemplate := `--- 907 title: doc%s 908 weight: %d 909 --- 910 C-%s` 911 912 templates = append(templates, []string{"shortcodes/c.html", contentShortcode}...) 913 templates = append(templates, []string{"_default/single.html", "Single Content: {{ .Content }}"}...) 914 templates = append(templates, []string{"_default/list.html", "List Content: {{ .Content }}"}...) 915 916 content = append(content, []string{"b1/index.md", fmt.Sprintf(contentWithShortcodeTemplate, "b1", 1)}...) 917 content = append(content, []string{"b1/logo.png", "PNG logo"}...) 918 content = append(content, []string{"b1/bp1.md", fmt.Sprintf(simpleContentTemplate, "bp1", 1, "bp1")}...) 919 920 content = append(content, []string{"section1/_index.md", fmt.Sprintf(contentWithShortcodeTemplate, "s1", 2)}...) 921 content = append(content, []string{"section1/p1.md", fmt.Sprintf(simpleContentTemplate, "s1p1", 2, "s1p1")}...) 922 923 content = append(content, []string{"section2/_index.md", fmt.Sprintf(simpleContentTemplate, "b1", 1, "b1")}...) 924 content = append(content, []string{"section2/s2p1.md", fmt.Sprintf(contentWithShortcodeTemplate, "bp1", 1)}...) 925 926 builder := newTestSitesBuilder(t).WithDefaultMultiSiteConfig() 927 928 builder.WithContent(content...).WithTemplates(templates...).CreateSites().Build(BuildCfg{}) 929 s := builder.H.Sites[0] 930 builder.Assert(len(s.RegularPages()), qt.Equals, 3) 931 932 builder.AssertFileContent("public/en/section1/index.html", 933 "List Content: <p>Logo:P1:|P2:logo.png/PNG logo|:P1: P1:|P2:docs1p1/<p>C-s1p1</p>\n|", 934 "BP1:P1:|P2:docbp1/<p>C-bp1</p>", 935 ) 936 937 builder.AssertFileContent("public/en/b1/index.html", 938 "Single Content: <p>Logo:P1:|P2:logo.png/PNG logo|:P1: P1:|P2:docs1p1/<p>C-s1p1</p>\n|", 939 "P2:docbp1/<p>C-bp1</p>", 940 ) 941 942 builder.AssertFileContent("public/en/section2/s2p1/index.html", 943 "Single Content: <p>Logo:P1:|P2:logo.png/PNG logo|:P1: P1:|P2:docs1p1/<p>C-s1p1</p>\n|", 944 "P2:docbp1/<p>C-bp1</p>", 945 ) 946 } 947 948 // https://github.com/gohugoio/hugo/issues/5833 949 func TestShortcodeParentResourcesOnRebuild(t *testing.T) { 950 t.Parallel() 951 952 b := newTestSitesBuilder(t).Running().WithSimpleConfigFile() 953 b.WithTemplatesAdded( 954 "index.html", ` 955 {{ $b := .Site.GetPage "b1" }} 956 b1 Content: {{ $b.Content }} 957 {{$p := $b.Resources.GetMatch "p1*" }} 958 Content: {{ $p.Content }} 959 {{ $article := .Site.GetPage "blog/article" }} 960 Article Content: {{ $article.Content }} 961 `, 962 "shortcodes/c.html", ` 963 {{ range .Page.Parent.Resources }} 964 * Parent resource: {{ .Name }}: {{ .RelPermalink }} 965 {{ end }} 966 `) 967 968 pageContent := ` 969 --- 970 title: MyPage 971 --- 972 973 SHORTCODE: {{< c >}} 974 975 ` 976 977 b.WithContent("b1/index.md", pageContent, 978 "b1/logo.png", "PNG logo", 979 "b1/p1.md", pageContent, 980 "blog/_index.md", pageContent, 981 "blog/logo-article.png", "PNG logo", 982 "blog/article.md", pageContent, 983 ) 984 985 b.Build(BuildCfg{}) 986 987 assert := func(matchers ...string) { 988 allMatchers := append(matchers, "Parent resource: logo.png: /b1/logo.png", 989 "Article Content: <p>SHORTCODE: \n\n* Parent resource: logo-article.png: /blog/logo-article.png", 990 ) 991 992 b.AssertFileContent("public/index.html", 993 allMatchers..., 994 ) 995 } 996 997 assert() 998 999 b.EditFiles("content/b1/index.md", pageContent+" Edit.") 1000 1001 b.Build(BuildCfg{}) 1002 1003 assert("Edit.") 1004 } 1005 1006 func TestShortcodePreserveOrder(t *testing.T) { 1007 t.Parallel() 1008 c := qt.New(t) 1009 1010 contentTemplate := `--- 1011 title: doc%d 1012 weight: %d 1013 --- 1014 # doc 1015 1016 {{< s1 >}}{{< s2 >}}{{< s3 >}}{{< s4 >}}{{< s5 >}} 1017 1018 {{< nested >}} 1019 {{< ordinal >}} {{< scratch >}} 1020 {{< ordinal >}} {{< scratch >}} 1021 {{< ordinal >}} {{< scratch >}} 1022 {{< /nested >}} 1023 1024 ` 1025 1026 ordinalShortcodeTemplate := `ordinal: {{ .Ordinal }}{{ .Page.Scratch.Set "ordinal" .Ordinal }}` 1027 1028 nestedShortcode := `outer ordinal: {{ .Ordinal }} inner: {{ .Inner }}` 1029 scratchGetShortcode := `scratch ordinal: {{ .Ordinal }} scratch get ordinal: {{ .Page.Scratch.Get "ordinal" }}` 1030 shortcodeTemplate := `v%d: {{ .Ordinal }} sgo: {{ .Page.Scratch.Get "o2" }}{{ .Page.Scratch.Set "o2" .Ordinal }}|` 1031 1032 var shortcodes []string 1033 var content []string 1034 1035 shortcodes = append(shortcodes, []string{"shortcodes/nested.html", nestedShortcode}...) 1036 shortcodes = append(shortcodes, []string{"shortcodes/ordinal.html", ordinalShortcodeTemplate}...) 1037 shortcodes = append(shortcodes, []string{"shortcodes/scratch.html", scratchGetShortcode}...) 1038 1039 for i := 1; i <= 5; i++ { 1040 sc := fmt.Sprintf(shortcodeTemplate, i) 1041 sc = strings.Replace(sc, "%%", "%", -1) 1042 shortcodes = append(shortcodes, []string{fmt.Sprintf("shortcodes/s%d.html", i), sc}...) 1043 } 1044 1045 for i := 1; i <= 3; i++ { 1046 content = append(content, []string{fmt.Sprintf("p%d.md", i), fmt.Sprintf(contentTemplate, i, i)}...) 1047 } 1048 1049 builder := newTestSitesBuilder(t).WithDefaultMultiSiteConfig() 1050 1051 builder.WithContent(content...).WithTemplatesAdded(shortcodes...).CreateSites().Build(BuildCfg{}) 1052 1053 s := builder.H.Sites[0] 1054 c.Assert(len(s.RegularPages()), qt.Equals, 3) 1055 1056 builder.AssertFileContent("public/en/p1/index.html", `v1: 0 sgo: |v2: 1 sgo: 0|v3: 2 sgo: 1|v4: 3 sgo: 2|v5: 4 sgo: 3`) 1057 builder.AssertFileContent("public/en/p1/index.html", `outer ordinal: 5 inner: 1058 ordinal: 0 scratch ordinal: 1 scratch get ordinal: 0 1059 ordinal: 2 scratch ordinal: 3 scratch get ordinal: 2 1060 ordinal: 4 scratch ordinal: 5 scratch get ordinal: 4`) 1061 } 1062 1063 func TestShortcodeVariables(t *testing.T) { 1064 t.Parallel() 1065 c := qt.New(t) 1066 1067 builder := newTestSitesBuilder(t).WithSimpleConfigFile() 1068 1069 builder.WithContent("page.md", `--- 1070 title: "Hugo Rocks!" 1071 --- 1072 1073 # doc 1074 1075 {{< s1 >}} 1076 1077 `).WithTemplatesAdded("layouts/shortcodes/s1.html", ` 1078 Name: {{ .Name }} 1079 {{ with .Position }} 1080 File: {{ .Filename }} 1081 Offset: {{ .Offset }} 1082 Line: {{ .LineNumber }} 1083 Column: {{ .ColumnNumber }} 1084 String: {{ . | safeHTML }} 1085 {{ end }} 1086 1087 `).CreateSites().Build(BuildCfg{}) 1088 1089 s := builder.H.Sites[0] 1090 c.Assert(len(s.RegularPages()), qt.Equals, 1) 1091 1092 builder.AssertFileContent("public/page/index.html", 1093 filepath.FromSlash("File: content/page.md"), 1094 "Line: 7", "Column: 4", "Offset: 40", 1095 filepath.FromSlash("String: \"content/page.md:7:4\""), 1096 "Name: s1", 1097 ) 1098 } 1099 1100 func TestInlineShortcodes(t *testing.T) { 1101 for _, enableInlineShortcodes := range []bool{true, false} { 1102 enableInlineShortcodes := enableInlineShortcodes 1103 t.Run(fmt.Sprintf("enableInlineShortcodes=%t", enableInlineShortcodes), 1104 func(t *testing.T) { 1105 t.Parallel() 1106 conf := fmt.Sprintf(` 1107 baseURL = "https://example.com" 1108 enableInlineShortcodes = %t 1109 `, enableInlineShortcodes) 1110 1111 b := newTestSitesBuilder(t) 1112 b.WithConfigFile("toml", conf) 1113 1114 shortcodeContent := `FIRST:{{< myshort.inline "first" >}} 1115 Page: {{ .Page.Title }} 1116 Seq: {{ seq 3 }} 1117 Param: {{ .Get 0 }} 1118 {{< /myshort.inline >}}:END: 1119 1120 SECOND:{{< myshort.inline "second" />}}:END 1121 NEW INLINE: {{< n1.inline "5" >}}W1: {{ seq (.Get 0) }}{{< /n1.inline >}}:END: 1122 INLINE IN INNER: {{< outer >}}{{< n2.inline >}}W2: {{ seq 4 }}{{< /n2.inline >}}{{< /outer >}}:END: 1123 REUSED INLINE IN INNER: {{< outer >}}{{< n1.inline "3" />}}{{< /outer >}}:END: 1124 ## MARKDOWN DELIMITER: {{% mymarkdown.inline %}}**Hugo Rocks!**{{% /mymarkdown.inline %}} 1125 ` 1126 1127 b.WithContent("page-md-shortcode.md", `--- 1128 title: "Hugo" 1129 --- 1130 `+shortcodeContent) 1131 1132 b.WithContent("_index.md", `--- 1133 title: "Hugo Home" 1134 --- 1135 1136 `+shortcodeContent) 1137 1138 b.WithTemplatesAdded("layouts/_default/single.html", ` 1139 CONTENT:{{ .Content }} 1140 TOC: {{ .TableOfContents }} 1141 `) 1142 1143 b.WithTemplatesAdded("layouts/index.html", ` 1144 CONTENT:{{ .Content }} 1145 TOC: {{ .TableOfContents }} 1146 `) 1147 1148 b.WithTemplatesAdded("layouts/shortcodes/outer.html", `Inner: {{ .Inner }}`) 1149 1150 b.CreateSites().Build(BuildCfg{}) 1151 1152 shouldContain := []string{ 1153 "Seq: [1 2 3]", 1154 "Param: first", 1155 "Param: second", 1156 "NEW INLINE: W1: [1 2 3 4 5]", 1157 "INLINE IN INNER: Inner: W2: [1 2 3 4]", 1158 "REUSED INLINE IN INNER: Inner: W1: [1 2 3]", 1159 `<li><a href="#markdown-delimiter-hugo-rocks">MARKDOWN DELIMITER: <strong>Hugo Rocks!</strong></a></li>`, 1160 } 1161 1162 if enableInlineShortcodes { 1163 b.AssertFileContent("public/page-md-shortcode/index.html", 1164 shouldContain..., 1165 ) 1166 b.AssertFileContent("public/index.html", 1167 shouldContain..., 1168 ) 1169 } else { 1170 b.AssertFileContent("public/page-md-shortcode/index.html", 1171 "FIRST::END", 1172 "SECOND::END", 1173 "NEW INLINE: :END", 1174 "INLINE IN INNER: Inner: :END:", 1175 "REUSED INLINE IN INNER: Inner: :END:", 1176 ) 1177 } 1178 }) 1179 1180 } 1181 } 1182 1183 // https://github.com/gohugoio/hugo/issues/5863 1184 func TestShortcodeNamespaced(t *testing.T) { 1185 t.Parallel() 1186 c := qt.New(t) 1187 1188 builder := newTestSitesBuilder(t).WithSimpleConfigFile() 1189 1190 builder.WithContent("page.md", `--- 1191 title: "Hugo Rocks!" 1192 --- 1193 1194 # doc 1195 1196 hello: {{< hello >}} 1197 test/hello: {{< test/hello >}} 1198 1199 `).WithTemplatesAdded( 1200 "layouts/shortcodes/hello.html", `hello`, 1201 "layouts/shortcodes/test/hello.html", `test/hello`).CreateSites().Build(BuildCfg{}) 1202 1203 s := builder.H.Sites[0] 1204 c.Assert(len(s.RegularPages()), qt.Equals, 1) 1205 1206 builder.AssertFileContent("public/page/index.html", 1207 "hello: hello", 1208 "test/hello: test/hello", 1209 ) 1210 } 1211 1212 // https://github.com/gohugoio/hugo/issues/6504 1213 func TestShortcodeEmoji(t *testing.T) { 1214 t.Parallel() 1215 1216 v := config.New() 1217 v.Set("enableEmoji", true) 1218 1219 builder := newTestSitesBuilder(t).WithViper(v) 1220 1221 builder.WithContent("page.md", `--- 1222 title: "Hugo Rocks!" 1223 --- 1224 1225 # doc 1226 1227 {{< event >}}10:30-11:00 My :smile: Event {{< /event >}} 1228 1229 1230 `).WithTemplatesAdded( 1231 "layouts/shortcodes/event.html", `<div>{{ "\u29BE" }} {{ .Inner }} </div>`) 1232 1233 builder.Build(BuildCfg{}) 1234 builder.AssertFileContent("public/page/index.html", 1235 "⦾ 10:30-11:00 My 😄 Event", 1236 ) 1237 } 1238 1239 func TestShortcodeTypedParams(t *testing.T) { 1240 t.Parallel() 1241 c := qt.New(t) 1242 1243 builder := newTestSitesBuilder(t).WithSimpleConfigFile() 1244 1245 builder.WithContent("page.md", `--- 1246 title: "Hugo Rocks!" 1247 --- 1248 1249 # doc 1250 1251 types positional: {{< hello true false 33 3.14 >}} 1252 types named: {{< hello b1=true b2=false i1=33 f1=3.14 >}} 1253 types string: {{< hello "true" trues "33" "3.14" >}} 1254 1255 1256 `).WithTemplatesAdded( 1257 "layouts/shortcodes/hello.html", 1258 `{{ range $i, $v := .Params }} 1259 - {{ printf "%v: %v (%T)" $i $v $v }} 1260 {{ end }} 1261 {{ $b1 := .Get "b1" }} 1262 Get: {{ printf "%v (%T)" $b1 $b1 | safeHTML }} 1263 `).Build(BuildCfg{}) 1264 1265 s := builder.H.Sites[0] 1266 c.Assert(len(s.RegularPages()), qt.Equals, 1) 1267 1268 builder.AssertFileContent("public/page/index.html", 1269 "types positional: - 0: true (bool) - 1: false (bool) - 2: 33 (int) - 3: 3.14 (float64)", 1270 "types named: - b1: true (bool) - b2: false (bool) - f1: 3.14 (float64) - i1: 33 (int) Get: true (bool) ", 1271 "types string: - 0: true (string) - 1: trues (string) - 2: 33 (string) - 3: 3.14 (string) ", 1272 ) 1273 } 1274 1275 func TestShortcodeRef(t *testing.T) { 1276 for _, plainIDAnchors := range []bool{false, true} { 1277 plainIDAnchors := plainIDAnchors 1278 t.Run(fmt.Sprintf("plainIDAnchors=%t", plainIDAnchors), func(t *testing.T) { 1279 t.Parallel() 1280 1281 v := config.New() 1282 v.Set("baseURL", "https://example.org") 1283 v.Set("blackfriday", map[string]interface{}{ 1284 "plainIDAnchors": plainIDAnchors, 1285 }) 1286 v.Set("markup", map[string]interface{}{ 1287 "defaultMarkdownHandler": "blackfriday", // TODO(bep) 1288 }) 1289 1290 builder := newTestSitesBuilder(t).WithViper(v) 1291 1292 for i := 1; i <= 2; i++ { 1293 builder.WithContent(fmt.Sprintf("page%d.md", i), `--- 1294 title: "Hugo Rocks!" 1295 --- 1296 1297 1298 1299 [Page 1]({{< ref "page1.md" >}}) 1300 [Page 1 with anchor]({{< relref "page1.md#doc" >}}) 1301 [Page 2]({{< ref "page2.md" >}}) 1302 [Page 2 with anchor]({{< relref "page2.md#doc" >}}) 1303 1304 1305 ## Doc 1306 1307 1308 `) 1309 } 1310 1311 builder.Build(BuildCfg{}) 1312 1313 if plainIDAnchors { 1314 builder.AssertFileContent("public/page2/index.html", 1315 ` 1316 <a href="/page1/#doc">Page 1 with anchor</a> 1317 <a href="https://example.org/page2/">Page 2</a> 1318 <a href="/page2/#doc">Page 2 with anchor</a></p> 1319 1320 <h2 id="doc">Doc</h2> 1321 `, 1322 ) 1323 } else { 1324 builder.AssertFileContent("public/page2/index.html", 1325 ` 1326 <p><a href="https://example.org/page1/">Page 1</a> 1327 <a href="/page1/#doc:45ca767ba77bc1445a0acab74f80812f">Page 1 with anchor</a> 1328 <a href="https://example.org/page2/">Page 2</a> 1329 <a href="/page2/#doc:8e3cdf52fa21e33270c99433820e46bd">Page 2 with anchor</a></p> 1330 <h2 id="doc:8e3cdf52fa21e33270c99433820e46bd">Doc</h2> 1331 `, 1332 ) 1333 } 1334 }) 1335 } 1336 } 1337 1338 // https://github.com/gohugoio/hugo/issues/6857 1339 func TestShortcodeNoInner(t *testing.T) { 1340 t.Parallel() 1341 1342 b := newTestSitesBuilder(t) 1343 1344 b.WithContent("page.md", `--- 1345 title: "No Inner!" 1346 --- 1347 {{< noinner >}}{{< /noinner >}} 1348 1349 1350 `).WithTemplatesAdded( 1351 "layouts/shortcodes/noinner.html", `No inner here.`) 1352 1353 err := b.BuildE(BuildCfg{}) 1354 b.Assert(err.Error(), qt.Contains, `failed to extract shortcode: shortcode "noinner" has no .Inner, yet a closing tag was provided`) 1355 } 1356 1357 func TestShortcodeStableOutputFormatTemplates(t *testing.T) { 1358 t.Parallel() 1359 1360 for i := 0; i < 5; i++ { 1361 1362 b := newTestSitesBuilder(t) 1363 1364 const numPages = 10 1365 1366 for i := 0; i < numPages; i++ { 1367 b.WithContent(fmt.Sprintf("page%d.md", i), `--- 1368 title: "Page" 1369 outputs: ["html", "css", "csv", "json"] 1370 --- 1371 {{< myshort >}} 1372 1373 `) 1374 } 1375 1376 b.WithTemplates( 1377 "_default/single.html", "{{ .Content }}", 1378 "_default/single.css", "{{ .Content }}", 1379 "_default/single.csv", "{{ .Content }}", 1380 "_default/single.json", "{{ .Content }}", 1381 "shortcodes/myshort.html", `Short-HTML`, 1382 "shortcodes/myshort.csv", `Short-CSV`, 1383 ) 1384 1385 b.Build(BuildCfg{}) 1386 1387 //helpers.PrintFs(b.Fs.Destination, "public", os.Stdout) 1388 1389 for i := 0; i < numPages; i++ { 1390 b.AssertFileContent(fmt.Sprintf("public/page%d/index.html", i), "Short-HTML") 1391 b.AssertFileContent(fmt.Sprintf("public/page%d/index.csv", i), "Short-CSV") 1392 b.AssertFileContent(fmt.Sprintf("public/page%d/index.json", i), "Short-HTML") 1393 1394 } 1395 1396 for i := 0; i < numPages; i++ { 1397 b.AssertFileContent(fmt.Sprintf("public/page%d/styles.css", i), "Short-HTML") 1398 } 1399 1400 } 1401 }