github.com/neohugo/neohugo@v0.123.8/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 "context" 18 "fmt" 19 "path/filepath" 20 "reflect" 21 "strings" 22 "testing" 23 24 "github.com/neohugo/neohugo/config" 25 "github.com/neohugo/neohugo/resources/kinds" 26 27 "github.com/neohugo/neohugo/parser/pageparser" 28 29 qt "github.com/frankban/quicktest" 30 ) 31 32 func TestExtractShortcodes(t *testing.T) { 33 b := newTestSitesBuilder(t).WithSimpleConfigFile() 34 35 b.WithTemplates( 36 "default/single.html", `EMPTY`, 37 "_internal/shortcodes/tag.html", `tag`, 38 "_internal/shortcodes/legacytag.html", `{{ $_hugo_config := "{ \"version\": 1 }" }}tag`, 39 "_internal/shortcodes/sc1.html", `sc1`, 40 "_internal/shortcodes/sc2.html", `sc2`, 41 "_internal/shortcodes/inner.html", `{{with .Inner }}{{ . }}{{ end }}`, 42 "_internal/shortcodes/inner2.html", `{{.Inner}}`, 43 "_internal/shortcodes/inner3.html", `{{.Inner}}`, 44 ).WithContent("page.md", `--- 45 title: "Shortcodes Galore!" 46 --- 47 `) 48 49 b.CreateSites().Build(BuildCfg{}) 50 51 s := b.H.Sites[0] 52 53 // Make it more regexp friendly 54 strReplacer := strings.NewReplacer("[", "{", "]", "}") 55 56 str := func(s *shortcode) string { 57 if s == nil { 58 return "<nil>" 59 } 60 61 var version int 62 if s.info != nil { 63 version = s.info.ParseInfo().Config.Version 64 } 65 return strReplacer.Replace(fmt.Sprintf("%s;inline:%t;closing:%t;inner:%v;params:%v;ordinal:%d;markup:%t;version:%d;pos:%d", 66 s.name, s.isInline, s.isClosing, s.inner, s.params, s.ordinal, s.doMarkup, version, s.pos)) 67 } 68 69 regexpCheck := func(re string) func(c *qt.C, shortcode *shortcode, err error) { 70 return func(c *qt.C, shortcode *shortcode, err error) { 71 c.Assert(err, qt.IsNil) 72 c.Assert(str(shortcode), qt.Matches, ".*"+re+".*") 73 } 74 } 75 76 for _, test := range []struct { 77 name string 78 input string 79 check func(c *qt.C, shortcode *shortcode, err error) 80 }{ 81 {"one shortcode, no markup", "{{< tag >}}", regexpCheck("tag.*closing:false.*markup:false")}, 82 {"one shortcode, markup", "{{% tag %}}", regexpCheck("tag.*closing:false.*markup:true;version:2")}, 83 {"one shortcode, markup, legacy", "{{% legacytag %}}", regexpCheck("tag.*closing:false.*markup:true;version:1")}, 84 {"outer shortcode markup", "{{% inner %}}{{< tag >}}{{% /inner %}}", regexpCheck("inner.*closing:true.*markup:true")}, 85 {"inner shortcode markup", "{{< inner >}}{{% tag %}}{{< /inner >}}", regexpCheck("inner.*closing:true.*;markup:false;version:2")}, 86 {"one pos param", "{{% tag param1 %}}", regexpCheck("tag.*params:{param1}")}, 87 {"two pos params", "{{< tag param1 param2>}}", regexpCheck("tag.*params:{param1 param2}")}, 88 {"one named param", `{{% tag param1="value" %}}`, regexpCheck("tag.*params:map{param1:value}")}, 89 {"two named params", `{{< tag param1="value1" param2="value2" >}}`, regexpCheck("tag.*params:map{param\\d:value\\d param\\d:value\\d}")}, 90 {"inner", `{{< inner >}}Inner Content{{< / inner >}}`, regexpCheck("inner;inline:false;closing:true;inner:{Inner Content};")}, 91 // issue #934 92 {"inner self-closing", `{{< inner />}}`, regexpCheck("inner;.*inner:{}")}, 93 { 94 "nested inner", `{{< inner >}}Inner Content->{{% inner2 param1 %}}inner2txt{{% /inner2 %}}Inner close->{{< / inner >}}`, 95 regexpCheck("inner;.*inner:{Inner Content->.*Inner close->}"), 96 }, 97 { 98 "nested, nested inner", `{{< inner >}}inner2->{{% inner2 param1 %}}inner2txt->inner3{{< inner3>}}inner3txt{{</ inner3 >}}{{% /inner2 %}}final close->{{< / inner >}}`, 99 regexpCheck("inner:{inner2-> inner2.*{{inner2txt->inner3.*final close->}"), 100 }, 101 {"closed without content", `{{< inner param1 >}}{{< / inner >}}`, regexpCheck("inner.*inner:{}")}, 102 {"inline", `{{< my.inline >}}Hi{{< /my.inline >}}`, regexpCheck("my.inline;inline:true;closing:true;inner:{Hi};")}, 103 } { 104 105 test := test 106 107 t.Run(test.name, func(t *testing.T) { 108 t.Parallel() 109 c := qt.New(t) 110 111 p, err := pageparser.ParseMain(strings.NewReader(test.input), pageparser.Config{}) 112 c.Assert(err, qt.IsNil) 113 handler := newShortcodeHandler("", s) 114 iter := p.Iterator() 115 116 short, err := handler.extractShortcode(0, 0, p.Input(), iter) 117 118 test.check(c, short, err) 119 }) 120 } 121 } 122 123 func TestShortcodeMultipleOutputFormats(t *testing.T) { 124 t.Parallel() 125 126 siteConfig := ` 127 baseURL = "http://example.com/blog" 128 129 paginate = 1 130 131 disableKinds = ["section", "term", "taxonomy", "RSS", "sitemap", "robotsTXT", "404"] 132 133 [outputs] 134 home = [ "HTML", "AMP", "Calendar" ] 135 page = [ "HTML", "AMP", "JSON" ] 136 137 ` 138 139 pageTemplate := `--- 140 title: "%s" 141 --- 142 # Doc 143 144 {{< myShort >}} 145 {{< noExt >}} 146 {{%% onlyHTML %%}} 147 148 {{< myInner >}}{{< myShort >}}{{< /myInner >}} 149 150 ` 151 152 pageTemplateCSVOnly := `--- 153 title: "%s" 154 outputs: ["CSV"] 155 --- 156 # Doc 157 158 CSV: {{< myShort >}} 159 ` 160 161 b := newTestSitesBuilder(t).WithConfigFile("toml", siteConfig) 162 b.WithTemplates( 163 "layouts/_default/single.html", `Single HTML: {{ .Title }}|{{ .Content }}`, 164 "layouts/_default/single.json", `Single JSON: {{ .Title }}|{{ .Content }}`, 165 "layouts/_default/single.csv", `Single CSV: {{ .Title }}|{{ .Content }}`, 166 "layouts/index.html", `Home HTML: {{ .Title }}|{{ .Content }}`, 167 "layouts/index.amp.html", `Home AMP: {{ .Title }}|{{ .Content }}`, 168 "layouts/index.ics", `Home Calendar: {{ .Title }}|{{ .Content }}`, 169 "layouts/shortcodes/myShort.html", `ShortHTML`, 170 "layouts/shortcodes/myShort.amp.html", `ShortAMP`, 171 "layouts/shortcodes/myShort.csv", `ShortCSV`, 172 "layouts/shortcodes/myShort.ics", `ShortCalendar`, 173 "layouts/shortcodes/myShort.json", `ShortJSON`, 174 "layouts/shortcodes/noExt", `ShortNoExt`, 175 "layouts/shortcodes/onlyHTML.html", `ShortOnlyHTML`, 176 "layouts/shortcodes/myInner.html", `myInner:--{{- .Inner -}}--`, 177 ) 178 179 b.WithContent("_index.md", fmt.Sprintf(pageTemplate, "Home"), 180 "sect/mypage.md", fmt.Sprintf(pageTemplate, "Single"), 181 "sect/mycsvpage.md", fmt.Sprintf(pageTemplateCSVOnly, "Single CSV"), 182 ) 183 184 b.Build(BuildCfg{}) 185 h := b.H 186 b.Assert(len(h.Sites), qt.Equals, 1) 187 188 s := h.Sites[0] 189 home := s.getPageOldVersion(kinds.KindHome) 190 b.Assert(home, qt.Not(qt.IsNil)) 191 b.Assert(len(home.OutputFormats()), qt.Equals, 3) 192 193 b.AssertFileContent("public/index.html", 194 "Home HTML", 195 "ShortHTML", 196 "ShortNoExt", 197 "ShortOnlyHTML", 198 "myInner:--ShortHTML--", 199 ) 200 201 b.AssertFileContent("public/amp/index.html", 202 "Home AMP", 203 "ShortAMP", 204 "ShortNoExt", 205 "ShortOnlyHTML", 206 "myInner:--ShortAMP--", 207 ) 208 209 b.AssertFileContent("public/index.ics", 210 "Home Calendar", 211 "ShortCalendar", 212 "ShortNoExt", 213 "ShortOnlyHTML", 214 "myInner:--ShortCalendar--", 215 ) 216 217 b.AssertFileContent("public/sect/mypage/index.html", 218 "Single HTML", 219 "ShortHTML", 220 "ShortNoExt", 221 "ShortOnlyHTML", 222 "myInner:--ShortHTML--", 223 ) 224 225 b.AssertFileContent("public/sect/mypage/index.json", 226 "Single JSON", 227 "ShortJSON", 228 "ShortNoExt", 229 "ShortOnlyHTML", 230 "myInner:--ShortJSON--", 231 ) 232 233 b.AssertFileContent("public/amp/sect/mypage/index.html", 234 // No special AMP template 235 "Single HTML", 236 "ShortAMP", 237 "ShortNoExt", 238 "ShortOnlyHTML", 239 "myInner:--ShortAMP--", 240 ) 241 242 b.AssertFileContent("public/sect/mycsvpage/index.csv", 243 "Single CSV", 244 "ShortCSV", 245 ) 246 } 247 248 func BenchmarkReplaceShortcodeTokens(b *testing.B) { 249 type input struct { 250 in []byte 251 tokenHandler func(ctx context.Context, token string) ([]byte, error) 252 expect []byte 253 } 254 255 data := []struct { 256 input string 257 replacements map[string]string 258 expect []byte 259 }{ 260 {"Hello HAHAHUGOSHORTCODE-1HBHB.", map[string]string{"HAHAHUGOSHORTCODE-1HBHB": "World"}, []byte("Hello World.")}, 261 {strings.Repeat("A", 100) + " HAHAHUGOSHORTCODE-1HBHB.", map[string]string{"HAHAHUGOSHORTCODE-1HBHB": "Hello World"}, []byte(strings.Repeat("A", 100) + " Hello World.")}, 262 {strings.Repeat("A", 500) + " HAHAHUGOSHORTCODE-1HBHB.", map[string]string{"HAHAHUGOSHORTCODE-1HBHB": "Hello World"}, []byte(strings.Repeat("A", 500) + " Hello World.")}, 263 {strings.Repeat("ABCD ", 500) + " HAHAHUGOSHORTCODE-1HBHB.", map[string]string{"HAHAHUGOSHORTCODE-1HBHB": "Hello World"}, []byte(strings.Repeat("ABCD ", 500) + " Hello World.")}, 264 {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.")}, 265 } 266 267 cnt := 0 268 in := make([]input, b.N*len(data)) 269 for i := 0; i < b.N; i++ { 270 for _, this := range data { 271 replacements := make(map[string]shortcodeRenderer) 272 for k, v := range this.replacements { 273 replacements[k] = prerenderedShortcode{s: v} 274 } 275 tokenHandler := func(ctx context.Context, token string) ([]byte, error) { 276 return []byte(this.replacements[token]), nil 277 } 278 in[cnt] = input{[]byte(this.input), tokenHandler, this.expect} 279 cnt++ 280 } 281 } 282 283 b.ResetTimer() 284 cnt = 0 285 ctx := context.Background() 286 for i := 0; i < b.N; i++ { 287 for j := range data { 288 currIn := in[cnt] 289 cnt++ 290 results, err := expandShortcodeTokens(ctx, currIn.in, currIn.tokenHandler) 291 if err != nil { 292 b.Fatalf("[%d] failed: %s", i, err) 293 continue 294 } 295 if len(results) != len(currIn.expect) { 296 b.Fatalf("[%d] replaceShortcodeTokens, got \n%q but expected \n%q", j, results, currIn.expect) 297 } 298 299 } 300 } 301 } 302 303 func BenchmarkShortcodesInSite(b *testing.B) { 304 files := ` 305 -- config.toml -- 306 -- layouts/shortcodes/mark1.md -- 307 {{ .Inner }} 308 -- layouts/shortcodes/mark2.md -- 309 1. Item Mark2 1 310 1. Item Mark2 2 311 1. Item Mark2 2-1 312 1. Item Mark2 3 313 -- layouts/_default/single.html -- 314 {{ .Content }} 315 ` 316 317 content := ` 318 --- 319 title: "Markdown Shortcode" 320 --- 321 322 ## List 323 324 1. List 1 325 {{§ mark1 §}} 326 1. Item Mark1 1 327 1. Item Mark1 2 328 {{§ mark2 §}} 329 {{§ /mark1 §}} 330 331 ` 332 333 for i := 1; i < 100; i++ { 334 files += fmt.Sprintf("\n-- content/posts/p%d.md --\n"+content, i+1) 335 } 336 files = strings.ReplaceAll(files, "§", "%") 337 338 cfg := IntegrationTestConfig{ 339 T: b, 340 TxtarString: files, 341 } 342 builders := make([]*IntegrationTestBuilder, b.N) 343 344 for i := range builders { 345 builders[i] = NewIntegrationTestBuilder(cfg) 346 } 347 348 b.ResetTimer() 349 350 for i := 0; i < b.N; i++ { 351 builders[i].Build() 352 } 353 } 354 355 func TestReplaceShortcodeTokens(t *testing.T) { 356 t.Parallel() 357 for i, this := range []struct { 358 input string 359 prefix string 360 replacements map[string]string 361 expect any 362 }{ 363 {"Hello HAHAHUGOSHORTCODE-1HBHB.", "PREFIX", map[string]string{"HAHAHUGOSHORTCODE-1HBHB": "World"}, "Hello World."}, 364 {"Hello HAHAHUGOSHORTCODE-1@}@.", "PREFIX", map[string]string{"HAHAHUGOSHORTCODE-1HBHB": "World"}, false}, 365 {"HAHAHUGOSHORTCODE2-1HBHB", "PREFIX2", map[string]string{"HAHAHUGOSHORTCODE2-1HBHB": "World"}, "World"}, 366 {"Hello World!", "PREFIX2", map[string]string{}, "Hello World!"}, 367 {"!HAHAHUGOSHORTCODE-1HBHB", "PREFIX", map[string]string{"HAHAHUGOSHORTCODE-1HBHB": "World"}, "!World"}, 368 {"HAHAHUGOSHORTCODE-1HBHB!", "PREFIX", map[string]string{"HAHAHUGOSHORTCODE-1HBHB": "World"}, "World!"}, 369 {"!HAHAHUGOSHORTCODE-1HBHB!", "PREFIX", map[string]string{"HAHAHUGOSHORTCODE-1HBHB": "World"}, "!World!"}, 370 {"_{_PREFIX-1HBHB", "PREFIX", map[string]string{"HAHAHUGOSHORTCODE-1HBHB": "World"}, "_{_PREFIX-1HBHB"}, 371 {"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."}, 372 {"A HAHAHUGOSHORTCODE-1HBHB asdf HAHAHUGOSHORTCODE-2HBHB.", "A", map[string]string{"HAHAHUGOSHORTCODE-1HBHB": "v1", "HAHAHUGOSHORTCODE-2HBHB": "v2"}, "A v1 asdf v2."}, 373 {"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!."}, 374 {"A HAHAHUGOSHORTCODE-2HBHB HAHAHUGOSHORTCODE-1HBHB.", "PREFIX", map[string]string{"HAHAHUGOSHORTCODE-1HBHB": "A", "HAHAHUGOSHORTCODE-2HBHB": "B"}, "A B A."}, 375 {"A HAHAHUGOSHORTCODE-1HBHB HAHAHUGOSHORTCODE-2", "PREFIX", map[string]string{"HAHAHUGOSHORTCODE-1HBHB": "A"}, false}, 376 {"A HAHAHUGOSHORTCODE-1HBHB but not the second.", "PREFIX", map[string]string{"HAHAHUGOSHORTCODE-1HBHB": "A", "HAHAHUGOSHORTCODE-2HBHB": "B"}, "A A but not the second."}, 377 {"An HAHAHUGOSHORTCODE-1HBHB.", "PREFIX", map[string]string{"HAHAHUGOSHORTCODE-1HBHB": "A", "HAHAHUGOSHORTCODE-2HBHB": "B"}, "An A."}, 378 {"An HAHAHUGOSHORTCODE-1HBHB HAHAHUGOSHORTCODE-2HBHB.", "PREFIX", map[string]string{"HAHAHUGOSHORTCODE-1HBHB": "A", "HAHAHUGOSHORTCODE-2HBHB": "B"}, "An A B."}, 379 {"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."}, 380 {"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."}, 381 // Issue #1148 remove p-tags 10 => 382 {"Hello <p>HAHAHUGOSHORTCODE-1HBHB</p>. END.", "PREFIX", map[string]string{"HAHAHUGOSHORTCODE-1HBHB": "World"}, "Hello World. END."}, 383 {"Hello <p>HAHAHUGOSHORTCODE-1HBHB</p>. <p>HAHAHUGOSHORTCODE-2HBHB</p> END.", "PREFIX", map[string]string{"HAHAHUGOSHORTCODE-1HBHB": "World", "HAHAHUGOSHORTCODE-2HBHB": "THE"}, "Hello World. THE END."}, 384 {"Hello <p>HAHAHUGOSHORTCODE-1HBHB. END</p>.", "PREFIX", map[string]string{"HAHAHUGOSHORTCODE-1HBHB": "World"}, "Hello <p>World. END</p>."}, 385 {"<p>Hello HAHAHUGOSHORTCODE-1HBHB</p>. END.", "PREFIX", map[string]string{"HAHAHUGOSHORTCODE-1HBHB": "World"}, "<p>Hello World</p>. END."}, 386 {"Hello <p>HAHAHUGOSHORTCODE-1HBHB12", "PREFIX", map[string]string{"HAHAHUGOSHORTCODE-1HBHB": "World"}, "Hello <p>World12"}, 387 { 388 "Hello HAHAHUGOSHORTCODE-1HBHB. HAHAHUGOSHORTCODE-1HBHB-HAHAHUGOSHORTCODE-1HBHB HAHAHUGOSHORTCODE-1HBHB HAHAHUGOSHORTCODE-1HBHB HAHAHUGOSHORTCODE-1HBHB END", "P", 389 map[string]string{"HAHAHUGOSHORTCODE-1HBHB": strings.Repeat("BC", 100)}, 390 fmt.Sprintf("Hello %s. %s-%s %s %s %s END", 391 strings.Repeat("BC", 100), strings.Repeat("BC", 100), strings.Repeat("BC", 100), strings.Repeat("BC", 100), strings.Repeat("BC", 100), strings.Repeat("BC", 100)), 392 }, 393 } { 394 395 replacements := make(map[string]shortcodeRenderer) 396 for k, v := range this.replacements { 397 replacements[k] = prerenderedShortcode{s: v} 398 } 399 tokenHandler := func(ctx context.Context, token string) ([]byte, error) { 400 return []byte(this.replacements[token]), nil 401 } 402 403 ctx := context.Background() 404 results, err := expandShortcodeTokens(ctx, []byte(this.input), tokenHandler) 405 406 if b, ok := this.expect.(bool); ok && !b { 407 if err == nil { 408 t.Errorf("[%d] replaceShortcodeTokens didn't return an expected error", i) 409 } 410 } else { 411 if err != nil { 412 t.Errorf("[%d] failed: %s", i, err) 413 continue 414 } 415 if !reflect.DeepEqual(results, []byte(this.expect.(string))) { 416 t.Errorf("[%d] replaceShortcodeTokens, got \n%q but expected \n%q", i, results, this.expect) 417 } 418 } 419 420 } 421 } 422 423 func TestShortcodeGetContent(t *testing.T) { 424 t.Parallel() 425 426 contentShortcode := ` 427 {{- $t := .Get 0 -}} 428 {{- $p := .Get 1 -}} 429 {{- $k := .Get 2 -}} 430 {{- $page := $.Page.Site.GetPage "page" $p -}} 431 {{ if $page }} 432 {{- if eq $t "bundle" -}} 433 {{- .Scratch.Set "p" ($page.Resources.GetMatch (printf "%s*" $k)) -}} 434 {{- else -}} 435 {{- $.Scratch.Set "p" $page -}} 436 {{- end -}}P1:{{ .Page.Content }}|P2:{{ $p := ($.Scratch.Get "p") }}{{ $p.Title }}/{{ $p.Content }}| 437 {{- else -}} 438 {{- errorf "Page %s is nil" $p -}} 439 {{- end -}} 440 ` 441 442 var templates []string 443 var content []string 444 445 contentWithShortcodeTemplate := `--- 446 title: doc%s 447 weight: %d 448 --- 449 Logo:{{< c "bundle" "b1" "logo.png" >}}:P1: {{< c "page" "section1/p1" "" >}}:BP1:{{< c "bundle" "b1" "bp1" >}}` 450 451 simpleContentTemplate := `--- 452 title: doc%s 453 weight: %d 454 --- 455 C-%s` 456 457 templates = append(templates, []string{"shortcodes/c.html", contentShortcode}...) 458 templates = append(templates, []string{"_default/single.html", "Single Content: {{ .Content }}"}...) 459 templates = append(templates, []string{"_default/list.html", "List Content: {{ .Content }}"}...) 460 461 content = append(content, []string{"b1/index.md", fmt.Sprintf(contentWithShortcodeTemplate, "b1", 1)}...) 462 content = append(content, []string{"b1/logo.png", "PNG logo"}...) 463 content = append(content, []string{"b1/bp1.md", fmt.Sprintf(simpleContentTemplate, "bp1", 1, "bp1")}...) 464 465 content = append(content, []string{"section1/_index.md", fmt.Sprintf(contentWithShortcodeTemplate, "s1", 2)}...) 466 content = append(content, []string{"section1/p1.md", fmt.Sprintf(simpleContentTemplate, "s1p1", 2, "s1p1")}...) 467 468 content = append(content, []string{"section2/_index.md", fmt.Sprintf(simpleContentTemplate, "b1", 1, "b1")}...) 469 content = append(content, []string{"section2/s2p1.md", fmt.Sprintf(contentWithShortcodeTemplate, "bp1", 1)}...) 470 471 builder := newTestSitesBuilder(t).WithDefaultMultiSiteConfig() 472 473 builder.WithContent(content...).WithTemplates(templates...).CreateSites().Build(BuildCfg{}) 474 s := builder.H.Sites[0] 475 builder.Assert(len(s.RegularPages()), qt.Equals, 3) 476 477 builder.AssertFileContent("public/en/section1/index.html", 478 "List Content: <p>Logo:P1:|P2:logo.png/PNG logo|:P1: P1:|P2:docs1p1/<p>C-s1p1</p>\n|", 479 "BP1:P1:|P2:docbp1/<p>C-bp1</p>", 480 ) 481 482 builder.AssertFileContent("public/en/b1/index.html", 483 "Single Content: <p>Logo:P1:|P2:logo.png/PNG logo|:P1: P1:|P2:docs1p1/<p>C-s1p1</p>\n|", 484 "P2:docbp1/<p>C-bp1</p>", 485 ) 486 487 builder.AssertFileContent("public/en/section2/s2p1/index.html", 488 "Single Content: <p>Logo:P1:|P2:logo.png/PNG logo|:P1: P1:|P2:docs1p1/<p>C-s1p1</p>\n|", 489 "P2:docbp1/<p>C-bp1</p>", 490 ) 491 } 492 493 // https://github.com/neohugo/neohugo/issues/5833 494 func TestShortcodeParentResourcesOnRebuild(t *testing.T) { 495 t.Parallel() 496 497 b := newTestSitesBuilder(t).Running().WithSimpleConfigFile() 498 b.WithTemplatesAdded( 499 "index.html", ` 500 {{ $b := .Site.GetPage "b1" }} 501 b1 Content: {{ $b.Content }} 502 {{$p := $b.Resources.GetMatch "p1*" }} 503 Content: {{ $p.Content }} 504 {{ $article := .Site.GetPage "blog/article" }} 505 Article Content: {{ $article.Content }} 506 `, 507 "shortcodes/c.html", ` 508 {{ range .Page.Parent.Resources }} 509 * Parent resource: {{ .Name }}: {{ .RelPermalink }} 510 {{ end }} 511 `) 512 513 pageContent := ` 514 --- 515 title: MyPage 516 --- 517 518 SHORTCODE: {{< c >}} 519 520 ` 521 522 b.WithContent("b1/index.md", pageContent, 523 "b1/logo.png", "PNG logo", 524 "b1/p1.md", pageContent, 525 "blog/_index.md", pageContent, 526 "blog/logo-article.png", "PNG logo", 527 "blog/article.md", pageContent, 528 ) 529 530 b.Build(BuildCfg{}) 531 532 assert := func(matchers ...string) { 533 allMatchers := append(matchers, "Parent resource: logo.png: /b1/logo.png", 534 "Article Content: <p>SHORTCODE: \n\n* Parent resource: logo-article.png: /blog/logo-article.png", 535 ) 536 537 b.AssertFileContent("public/index.html", 538 allMatchers..., 539 ) 540 } 541 542 assert() 543 544 b.EditFiles("content/b1/index.md", pageContent+" Edit.") 545 546 b.Build(BuildCfg{}) 547 548 assert("Edit.") 549 } 550 551 func TestShortcodePreserveOrder(t *testing.T) { 552 t.Parallel() 553 c := qt.New(t) 554 555 contentTemplate := `--- 556 title: doc%d 557 weight: %d 558 --- 559 # doc 560 561 {{< s1 >}}{{< s2 >}}{{< s3 >}}{{< s4 >}}{{< s5 >}} 562 563 {{< nested >}} 564 {{< ordinal >}} {{< scratch >}} 565 {{< ordinal >}} {{< scratch >}} 566 {{< ordinal >}} {{< scratch >}} 567 {{< /nested >}} 568 569 ` 570 571 ordinalShortcodeTemplate := `ordinal: {{ .Ordinal }}{{ .Page.Scratch.Set "ordinal" .Ordinal }}` 572 573 nestedShortcode := `outer ordinal: {{ .Ordinal }} inner: {{ .Inner }}` 574 scratchGetShortcode := `scratch ordinal: {{ .Ordinal }} scratch get ordinal: {{ .Page.Scratch.Get "ordinal" }}` 575 shortcodeTemplate := `v%d: {{ .Ordinal }} sgo: {{ .Page.Scratch.Get "o2" }}{{ .Page.Scratch.Set "o2" .Ordinal }}|` 576 577 var shortcodes []string 578 var content []string 579 580 shortcodes = append(shortcodes, []string{"shortcodes/nested.html", nestedShortcode}...) 581 shortcodes = append(shortcodes, []string{"shortcodes/ordinal.html", ordinalShortcodeTemplate}...) 582 shortcodes = append(shortcodes, []string{"shortcodes/scratch.html", scratchGetShortcode}...) 583 584 for i := 1; i <= 5; i++ { 585 sc := fmt.Sprintf(shortcodeTemplate, i) 586 sc = strings.Replace(sc, "%%", "%", -1) 587 shortcodes = append(shortcodes, []string{fmt.Sprintf("shortcodes/s%d.html", i), sc}...) 588 } 589 590 for i := 1; i <= 3; i++ { 591 content = append(content, []string{fmt.Sprintf("p%d.md", i), fmt.Sprintf(contentTemplate, i, i)}...) 592 } 593 594 builder := newTestSitesBuilder(t).WithDefaultMultiSiteConfig() 595 596 builder.WithContent(content...).WithTemplatesAdded(shortcodes...).CreateSites().Build(BuildCfg{}) 597 598 s := builder.H.Sites[0] 599 c.Assert(len(s.RegularPages()), qt.Equals, 3) 600 601 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`) 602 builder.AssertFileContent("public/en/p1/index.html", `outer ordinal: 5 inner: 603 ordinal: 0 scratch ordinal: 1 scratch get ordinal: 0 604 ordinal: 2 scratch ordinal: 3 scratch get ordinal: 2 605 ordinal: 4 scratch ordinal: 5 scratch get ordinal: 4`) 606 } 607 608 func TestShortcodeVariables(t *testing.T) { 609 t.Parallel() 610 c := qt.New(t) 611 612 builder := newTestSitesBuilder(t).WithSimpleConfigFile() 613 614 builder.WithContent("page.md", `--- 615 title: "Hugo Rocks!" 616 --- 617 618 # doc 619 620 {{< s1 >}} 621 622 `).WithTemplatesAdded("layouts/shortcodes/s1.html", ` 623 Name: {{ .Name }} 624 {{ with .Position }} 625 File: {{ .Filename }} 626 Offset: {{ .Offset }} 627 Line: {{ .LineNumber }} 628 Column: {{ .ColumnNumber }} 629 String: {{ . | safeHTML }} 630 {{ end }} 631 632 `).CreateSites().Build(BuildCfg{}) 633 634 s := builder.H.Sites[0] 635 c.Assert(len(s.RegularPages()), qt.Equals, 1) 636 637 builder.AssertFileContent("public/page/index.html", 638 filepath.FromSlash("File: content/page.md"), 639 "Line: 7", "Column: 4", "Offset: 40", 640 filepath.FromSlash("String: \"content/page.md:7:4\""), 641 "Name: s1", 642 ) 643 } 644 645 func TestInlineShortcodes(t *testing.T) { 646 for _, enableInlineShortcodes := range []bool{true, false} { 647 enableInlineShortcodes := enableInlineShortcodes 648 t.Run(fmt.Sprintf("enableInlineShortcodes=%t", enableInlineShortcodes), 649 func(t *testing.T) { 650 t.Parallel() 651 conf := fmt.Sprintf(` 652 baseURL = "https://example.com" 653 enableInlineShortcodes = %t 654 `, enableInlineShortcodes) 655 656 b := newTestSitesBuilder(t) 657 b.WithConfigFile("toml", conf) 658 659 shortcodeContent := `FIRST:{{< myshort.inline "first" >}} 660 Page: {{ .Page.Title }} 661 Seq: {{ seq 3 }} 662 Param: {{ .Get 0 }} 663 {{< /myshort.inline >}}:END: 664 665 SECOND:{{< myshort.inline "second" />}}:END 666 NEW INLINE: {{< n1.inline "5" >}}W1: {{ seq (.Get 0) }}{{< /n1.inline >}}:END: 667 INLINE IN INNER: {{< outer >}}{{< n2.inline >}}W2: {{ seq 4 }}{{< /n2.inline >}}{{< /outer >}}:END: 668 REUSED INLINE IN INNER: {{< outer >}}{{< n1.inline "3" />}}{{< /outer >}}:END: 669 ## MARKDOWN DELIMITER: {{% mymarkdown.inline %}}**Hugo Rocks!**{{% /mymarkdown.inline %}} 670 ` 671 672 b.WithContent("page-md-shortcode.md", `--- 673 title: "Hugo" 674 --- 675 `+shortcodeContent) 676 677 b.WithContent("_index.md", `--- 678 title: "Hugo Home" 679 --- 680 681 `+shortcodeContent) 682 683 b.WithTemplatesAdded("layouts/_default/single.html", ` 684 CONTENT:{{ .Content }} 685 TOC: {{ .TableOfContents }} 686 `) 687 688 b.WithTemplatesAdded("layouts/index.html", ` 689 CONTENT:{{ .Content }} 690 TOC: {{ .TableOfContents }} 691 `) 692 693 b.WithTemplatesAdded("layouts/shortcodes/outer.html", `Inner: {{ .Inner }}`) 694 695 b.CreateSites().Build(BuildCfg{}) 696 697 shouldContain := []string{ 698 "Seq: [1 2 3]", 699 "Param: first", 700 "Param: second", 701 "NEW INLINE: W1: [1 2 3 4 5]", 702 "INLINE IN INNER: Inner: W2: [1 2 3 4]", 703 "REUSED INLINE IN INNER: Inner: W1: [1 2 3]", 704 `<li><a href="#markdown-delimiter-hugo-rocks">MARKDOWN DELIMITER: <strong>Hugo Rocks!</strong></a></li>`, 705 } 706 707 if enableInlineShortcodes { 708 b.AssertFileContent("public/page-md-shortcode/index.html", 709 shouldContain..., 710 ) 711 b.AssertFileContent("public/index.html", 712 shouldContain..., 713 ) 714 } else { 715 b.AssertFileContent("public/page-md-shortcode/index.html", 716 "FIRST::END", 717 "SECOND::END", 718 "NEW INLINE: :END", 719 "INLINE IN INNER: Inner: :END:", 720 "REUSED INLINE IN INNER: Inner: :END:", 721 ) 722 } 723 }) 724 725 } 726 } 727 728 // https://github.com/neohugo/neohugo/issues/5863 729 func TestShortcodeNamespaced(t *testing.T) { 730 t.Parallel() 731 c := qt.New(t) 732 733 builder := newTestSitesBuilder(t).WithSimpleConfigFile() 734 735 builder.WithContent("page.md", `--- 736 title: "Hugo Rocks!" 737 --- 738 739 # doc 740 741 hello: {{< hello >}} 742 test/hello: {{< test/hello >}} 743 744 `).WithTemplatesAdded( 745 "layouts/shortcodes/hello.html", `hello`, 746 "layouts/shortcodes/test/hello.html", `test/hello`).CreateSites().Build(BuildCfg{}) 747 748 s := builder.H.Sites[0] 749 c.Assert(len(s.RegularPages()), qt.Equals, 1) 750 751 builder.AssertFileContent("public/page/index.html", 752 "hello: hello", 753 "test/hello: test/hello", 754 ) 755 } 756 757 func TestShortcodeParams(t *testing.T) { 758 t.Parallel() 759 c := qt.New(t) 760 761 builder := newTestSitesBuilder(t).WithSimpleConfigFile() 762 763 builder.WithContent("page.md", `--- 764 title: "Hugo Rocks!" 765 --- 766 767 # doc 768 769 types positional: {{< hello true false 33 3.14 >}} 770 types named: {{< hello b1=true b2=false i1=33 f1=3.14 >}} 771 types string: {{< hello "true" trues "33" "3.14" >}} 772 escaped quoute: {{< hello "hello \"world\"." >}} 773 774 775 `).WithTemplatesAdded( 776 "layouts/shortcodes/hello.html", 777 `{{ range $i, $v := .Params }} 778 - {{ printf "%v: %v (%T)" $i $v $v }} 779 {{ end }} 780 {{ $b1 := .Get "b1" }} 781 Get: {{ printf "%v (%T)" $b1 $b1 | safeHTML }} 782 `).Build(BuildCfg{}) 783 784 s := builder.H.Sites[0] 785 c.Assert(len(s.RegularPages()), qt.Equals, 1) 786 787 builder.AssertFileContent("public/page/index.html", 788 "types positional: - 0: true (bool) - 1: false (bool) - 2: 33 (int) - 3: 3.14 (float64)", 789 "types named: - b1: true (bool) - b2: false (bool) - f1: 3.14 (float64) - i1: 33 (int) Get: true (bool) ", 790 "types string: - 0: true (string) - 1: trues (string) - 2: 33 (string) - 3: 3.14 (string) ", 791 "hello "world". (string)", 792 ) 793 } 794 795 func TestShortcodeRef(t *testing.T) { 796 t.Parallel() 797 798 v := config.New() 799 v.Set("baseURL", "https://example.org") 800 801 builder := newTestSitesBuilder(t).WithViper(v) 802 803 for i := 1; i <= 2; i++ { 804 builder.WithContent(fmt.Sprintf("page%d.md", i), `--- 805 title: "Hugo Rocks!" 806 --- 807 808 809 810 [Page 1]({{< ref "page1.md" >}}) 811 [Page 1 with anchor]({{< relref "page1.md#doc" >}}) 812 [Page 2]({{< ref "page2.md" >}}) 813 [Page 2 with anchor]({{< relref "page2.md#doc" >}}) 814 815 816 ## Doc 817 818 819 `) 820 } 821 822 builder.Build(BuildCfg{}) 823 824 builder.AssertFileContent("public/page2/index.html", ` 825 <a href="/page1/#doc">Page 1 with anchor</a> 826 <a href="https://example.org/page2/">Page 2</a> 827 <a href="/page2/#doc">Page 2 with anchor</a></p> 828 829 <h2 id="doc">Doc</h2> 830 `, 831 ) 832 } 833 834 // https://github.com/neohugo/neohugo/issues/6857 835 func TestShortcodeNoInner(t *testing.T) { 836 t.Parallel() 837 838 b := newTestSitesBuilder(t) 839 840 b.WithContent("mypage.md", `--- 841 title: "No Inner!" 842 --- 843 {{< noinner >}}{{< /noinner >}} 844 845 846 `).WithTemplatesAdded( 847 "layouts/shortcodes/noinner.html", `No inner here.`) 848 849 err := b.BuildE(BuildCfg{}) 850 b.Assert(err.Error(), qt.Contains, filepath.FromSlash(`"content/mypage.md:4:16": failed to extract shortcode: shortcode "noinner" does not evaluate .Inner or .InnerDeindent, yet a closing tag was provided`)) 851 } 852 853 func TestShortcodeStableOutputFormatTemplates(t *testing.T) { 854 t.Parallel() 855 856 for i := 0; i < 5; i++ { 857 858 b := newTestSitesBuilder(t) 859 860 const numPages = 10 861 862 for i := 0; i < numPages; i++ { 863 b.WithContent(fmt.Sprintf("page%d.md", i), `--- 864 title: "Page" 865 outputs: ["html", "css", "csv", "json"] 866 --- 867 {{< myshort >}} 868 869 `) 870 } 871 872 b.WithTemplates( 873 "_default/single.html", "{{ .Content }}", 874 "_default/single.css", "{{ .Content }}", 875 "_default/single.csv", "{{ .Content }}", 876 "_default/single.json", "{{ .Content }}", 877 "shortcodes/myshort.html", `Short-HTML`, 878 "shortcodes/myshort.csv", `Short-CSV`, 879 ) 880 881 b.Build(BuildCfg{}) 882 883 // helpers.PrintFs(b.Fs.Destination, "public", os.Stdout) 884 885 for i := 0; i < numPages; i++ { 886 b.AssertFileContent(fmt.Sprintf("public/page%d/index.html", i), "Short-HTML") 887 b.AssertFileContent(fmt.Sprintf("public/page%d/index.csv", i), "Short-CSV") 888 b.AssertFileContent(fmt.Sprintf("public/page%d/index.json", i), "Short-HTML") 889 890 } 891 892 for i := 0; i < numPages; i++ { 893 b.AssertFileContent(fmt.Sprintf("public/page%d/styles.css", i), "Short-HTML") 894 } 895 896 } 897 } 898 899 // #9821 900 func TestShortcodeMarkdownOutputFormat(t *testing.T) { 901 t.Parallel() 902 903 files := ` 904 -- config.toml -- 905 -- content/p1.md -- 906 --- 907 title: "p1" 908 --- 909 {{< foo >}} 910 # The below would have failed using the HTML template parser. 911 -- layouts/shortcodes/foo.md -- 912 §§§ 913 <x 914 §§§ 915 -- layouts/_default/single.html -- 916 {{ .Content }} 917 ` 918 919 b := Test(t, files) 920 921 b.AssertFileContent("public/p1/index.html", ` 922 <x 923 `) 924 } 925 926 func TestShortcodePreserveIndentation(t *testing.T) { 927 t.Parallel() 928 929 files := ` 930 -- config.toml -- 931 -- content/p1.md -- 932 --- 933 title: "p1" 934 --- 935 936 ## List With Indented Shortcodes 937 938 1. List 1 939 {{% mark1 %}} 940 1. Item Mark1 1 941 1. Item Mark1 2 942 {{% mark2 %}} 943 {{% /mark1 %}} 944 -- layouts/shortcodes/mark1.md -- 945 {{ .Inner }} 946 -- layouts/shortcodes/mark2.md -- 947 1. Item Mark2 1 948 1. Item Mark2 2 949 1. Item Mark2 2-1 950 1. Item Mark2 3 951 -- layouts/_default/single.html -- 952 {{ .Content }} 953 ` 954 955 b := Test(t, files) 956 957 b.AssertFileContent("public/p1/index.html", "<ol>\n<li>\n<p>List 1</p>\n<ol>\n<li>Item Mark1 1</li>\n<li>Item Mark1 2</li>\n<li>Item Mark2 1</li>\n<li>Item Mark2 2\n<ol>\n<li>Item Mark2 2-1</li>\n</ol>\n</li>\n<li>Item Mark2 3</li>\n</ol>\n</li>\n</ol>") 958 } 959 960 func TestShortcodeCodeblockIndent(t *testing.T) { 961 t.Parallel() 962 963 files := ` 964 -- config.toml -- 965 -- content/p1.md -- 966 --- 967 title: "p1" 968 --- 969 970 ## Code block 971 972 {{% code %}} 973 974 -- layouts/shortcodes/code.md -- 975 echo "foo"; 976 -- layouts/_default/single.html -- 977 {{ .Content }} 978 ` 979 980 b := Test(t, files) 981 982 b.AssertFileContent("public/p1/index.html", "<pre><code>echo "foo";\n</code></pre>") 983 } 984 985 func TestShortcodeHighlightDeindent(t *testing.T) { 986 t.Parallel() 987 988 files := ` 989 -- config.toml -- 990 [markup] 991 [markup.highlight] 992 codeFences = true 993 noClasses = false 994 -- content/p1.md -- 995 --- 996 title: "p1" 997 --- 998 999 ## Indent 5 Spaces 1000 1001 {{< highlight bash >}} 1002 line 1; 1003 line 2; 1004 line 3; 1005 {{< /highlight >}} 1006 1007 -- layouts/_default/single.html -- 1008 {{ .Content }} 1009 ` 1010 1011 b := Test(t, files) 1012 1013 b.AssertFileContent("public/p1/index.html", ` 1014 <pre><code> <div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="line"><span class="cl">line 1<span class="p">;</span> 1015 </span></span><span class="line"><span class="cl">line 2<span class="p">;</span> 1016 </span></span><span class="line"><span class="cl">line 3<span class="p">;</span></span></span></code></pre></div> 1017 </code></pre> 1018 1019 `) 1020 } 1021 1022 // Issue 10236. 1023 func TestShortcodeParamEscapedQuote(t *testing.T) { 1024 t.Parallel() 1025 1026 files := ` 1027 -- config.toml -- 1028 -- content/p1.md -- 1029 --- 1030 title: "p1" 1031 --- 1032 1033 {{< figure src="/media/spf13.jpg" title="Steve \"Francia\"." >}} 1034 1035 -- layouts/shortcodes/figure.html -- 1036 Title: {{ .Get "title" | safeHTML }} 1037 -- layouts/_default/single.html -- 1038 {{ .Content }} 1039 ` 1040 1041 b := NewIntegrationTestBuilder( 1042 IntegrationTestConfig{ 1043 T: t, 1044 TxtarString: files, 1045 1046 Verbose: true, 1047 }, 1048 ).Build() 1049 1050 b.AssertFileContent("public/p1/index.html", `Title: Steve "Francia".`) 1051 } 1052 1053 // Issue 10391. 1054 func TestNestedShortcodeCustomOutputFormat(t *testing.T) { 1055 t.Parallel() 1056 1057 files := ` 1058 -- config.toml -- 1059 1060 [outputFormats.Foobar] 1061 baseName = "foobar" 1062 isPlainText = true 1063 mediaType = "application/json" 1064 notAlternative = true 1065 1066 [languages.en] 1067 languageName = "English" 1068 1069 [languages.en.outputs] 1070 home = [ "HTML", "RSS", "Foobar" ] 1071 1072 [languages.fr] 1073 languageName = "Français" 1074 1075 [[module.mounts]] 1076 source = "content/en" 1077 target = "content" 1078 lang = "en" 1079 1080 [[module.mounts]] 1081 source = "content/fr" 1082 target = "content" 1083 lang = "fr" 1084 1085 -- layouts/_default/list.foobar.json -- 1086 {{- $.Scratch.Add "data" slice -}} 1087 {{- range (where .Site.AllPages "Kind" "!=" "home") -}} 1088 {{- $.Scratch.Add "data" (dict "content" (.Plain | truncate 10000) "type" .Type "full_url" .Permalink) -}} 1089 {{- end -}} 1090 {{- $.Scratch.Get "data" | jsonify -}} 1091 -- content/en/p1.md -- 1092 --- 1093 title: "p1" 1094 --- 1095 1096 ### More information 1097 1098 {{< tabs >}} 1099 {{% tab "Test" %}} 1100 1101 It's a test 1102 1103 {{% /tab %}} 1104 {{< /tabs >}} 1105 1106 -- content/fr/p2.md -- 1107 --- 1108 title: Test 1109 --- 1110 1111 ### Plus d'informations 1112 1113 {{< tabs >}} 1114 {{% tab "Test" %}} 1115 1116 C'est un test 1117 1118 {{% /tab %}} 1119 {{< /tabs >}} 1120 1121 -- layouts/shortcodes/tabs.html -- 1122 <div> 1123 <div class="tab-content">{{ .Inner }}</div> 1124 </div> 1125 1126 -- layouts/shortcodes/tab.html -- 1127 <div>{{ .Inner }}</div> 1128 1129 -- layouts/_default/single.html -- 1130 {{ .Content }} 1131 ` 1132 1133 b := NewIntegrationTestBuilder( 1134 IntegrationTestConfig{ 1135 T: t, 1136 TxtarString: files, 1137 1138 Verbose: true, 1139 }, 1140 ).Build() 1141 1142 b.AssertFileContent("public/fr/p2/index.html", `plus-dinformations`) 1143 } 1144 1145 // Issue 10671. 1146 func TestShortcodeInnerShouldBeEmptyWhenNotClosed(t *testing.T) { 1147 t.Parallel() 1148 1149 files := ` 1150 -- config.toml -- 1151 disableKinds = ["home", "taxonomy", "term"] 1152 -- content/p1.md -- 1153 --- 1154 title: "p1" 1155 --- 1156 1157 {{< sc "self-closing" />}} 1158 1159 Text. 1160 1161 {{< sc "closing-no-newline" >}}{{< /sc >}} 1162 1163 -- layouts/shortcodes/sc.html -- 1164 Inner: {{ .Get 0 }}: {{ len .Inner }} 1165 InnerDeindent: {{ .Get 0 }}: {{ len .InnerDeindent }} 1166 -- layouts/_default/single.html -- 1167 {{ .Content }} 1168 ` 1169 1170 b := NewIntegrationTestBuilder( 1171 IntegrationTestConfig{ 1172 T: t, 1173 TxtarString: files, 1174 1175 Verbose: true, 1176 }, 1177 ).Build() 1178 1179 b.AssertFileContent("public/p1/index.html", ` 1180 Inner: self-closing: 0 1181 InnerDeindent: self-closing: 0 1182 Inner: closing-no-newline: 0 1183 InnerDeindent: closing-no-newline: 0 1184 1185 `) 1186 } 1187 1188 // Issue 10675. 1189 func TestShortcodeErrorWhenItShouldBeClosed(t *testing.T) { 1190 t.Parallel() 1191 1192 files := ` 1193 -- config.toml -- 1194 disableKinds = ["home", "taxonomy", "term"] 1195 -- content/p1.md -- 1196 --- 1197 title: "p1" 1198 --- 1199 1200 {{< sc >}} 1201 1202 Text. 1203 1204 -- layouts/shortcodes/sc.html -- 1205 Inner: {{ .Get 0 }}: {{ len .Inner }} 1206 -- layouts/_default/single.html -- 1207 {{ .Content }} 1208 ` 1209 1210 b, err := NewIntegrationTestBuilder( 1211 IntegrationTestConfig{ 1212 T: t, 1213 TxtarString: files, 1214 1215 Verbose: true, 1216 }, 1217 ).BuildE() 1218 1219 b.Assert(err, qt.Not(qt.IsNil)) 1220 b.Assert(err.Error(), qt.Contains, `p1.md:5:1": failed to extract shortcode: shortcode "sc" must be closed or self-closed`) 1221 } 1222 1223 // Issue 10819. 1224 func TestShortcodeInCodeFenceHyphen(t *testing.T) { 1225 t.Parallel() 1226 1227 files := ` 1228 -- config.toml -- 1229 disableKinds = ["home", "taxonomy", "term"] 1230 -- content/p1.md -- 1231 --- 1232 title: "p1" 1233 --- 1234 1235 §§§go 1236 {{< sc >}} 1237 §§§ 1238 1239 Text. 1240 1241 -- layouts/shortcodes/sc.html -- 1242 Hello. 1243 -- layouts/_default/single.html -- 1244 {{ .Content }} 1245 ` 1246 1247 b := NewIntegrationTestBuilder( 1248 IntegrationTestConfig{ 1249 T: t, 1250 TxtarString: files, 1251 1252 Verbose: true, 1253 }, 1254 ).Build() 1255 1256 b.AssertFileContent("public/p1/index.html", "<span style=\"color:#a6e22e\">Hello.</span>") 1257 }