github.com/linchen2chris/hugo@v0.0.0-20230307053224-cec209389705/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/gohugoio/hugo/config" 25 26 "github.com/gohugoio/hugo/parser/pageparser" 27 "github.com/gohugoio/hugo/resources/page" 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(nil, 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.getPage(page.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/gohugoio/hugo/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/gohugoio/hugo/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 // https://github.com/gohugoio/hugo/issues/6504 758 func TestShortcodeEmoji(t *testing.T) { 759 t.Parallel() 760 761 v := config.NewWithTestDefaults() 762 v.Set("enableEmoji", true) 763 764 builder := newTestSitesBuilder(t).WithViper(v) 765 766 builder.WithContent("page.md", `--- 767 title: "Hugo Rocks!" 768 --- 769 770 # doc 771 772 {{< event >}}10:30-11:00 My :smile: Event {{< /event >}} 773 774 775 `).WithTemplatesAdded( 776 "layouts/shortcodes/event.html", `<div>{{ "\u29BE" }} {{ .Inner }} </div>`) 777 778 builder.Build(BuildCfg{}) 779 builder.AssertFileContent("public/page/index.html", 780 "⦾ 10:30-11:00 My 😄 Event", 781 ) 782 } 783 784 func TestShortcodeParams(t *testing.T) { 785 t.Parallel() 786 c := qt.New(t) 787 788 builder := newTestSitesBuilder(t).WithSimpleConfigFile() 789 790 builder.WithContent("page.md", `--- 791 title: "Hugo Rocks!" 792 --- 793 794 # doc 795 796 types positional: {{< hello true false 33 3.14 >}} 797 types named: {{< hello b1=true b2=false i1=33 f1=3.14 >}} 798 types string: {{< hello "true" trues "33" "3.14" >}} 799 escaped quoute: {{< hello "hello \"world\"." >}} 800 801 802 `).WithTemplatesAdded( 803 "layouts/shortcodes/hello.html", 804 `{{ range $i, $v := .Params }} 805 - {{ printf "%v: %v (%T)" $i $v $v }} 806 {{ end }} 807 {{ $b1 := .Get "b1" }} 808 Get: {{ printf "%v (%T)" $b1 $b1 | safeHTML }} 809 `).Build(BuildCfg{}) 810 811 s := builder.H.Sites[0] 812 c.Assert(len(s.RegularPages()), qt.Equals, 1) 813 814 builder.AssertFileContent("public/page/index.html", 815 "types positional: - 0: true (bool) - 1: false (bool) - 2: 33 (int) - 3: 3.14 (float64)", 816 "types named: - b1: true (bool) - b2: false (bool) - f1: 3.14 (float64) - i1: 33 (int) Get: true (bool) ", 817 "types string: - 0: true (string) - 1: trues (string) - 2: 33 (string) - 3: 3.14 (string) ", 818 "hello "world". (string)", 819 ) 820 } 821 822 func TestShortcodeRef(t *testing.T) { 823 t.Parallel() 824 825 v := config.NewWithTestDefaults() 826 v.Set("baseURL", "https://example.org") 827 828 builder := newTestSitesBuilder(t).WithViper(v) 829 830 for i := 1; i <= 2; i++ { 831 builder.WithContent(fmt.Sprintf("page%d.md", i), `--- 832 title: "Hugo Rocks!" 833 --- 834 835 836 837 [Page 1]({{< ref "page1.md" >}}) 838 [Page 1 with anchor]({{< relref "page1.md#doc" >}}) 839 [Page 2]({{< ref "page2.md" >}}) 840 [Page 2 with anchor]({{< relref "page2.md#doc" >}}) 841 842 843 ## Doc 844 845 846 `) 847 } 848 849 builder.Build(BuildCfg{}) 850 851 builder.AssertFileContent("public/page2/index.html", ` 852 <a href="/page1/#doc">Page 1 with anchor</a> 853 <a href="https://example.org/page2/">Page 2</a> 854 <a href="/page2/#doc">Page 2 with anchor</a></p> 855 856 <h2 id="doc">Doc</h2> 857 `, 858 ) 859 860 } 861 862 // https://github.com/gohugoio/hugo/issues/6857 863 func TestShortcodeNoInner(t *testing.T) { 864 t.Parallel() 865 866 b := newTestSitesBuilder(t) 867 868 b.WithContent("mypage.md", `--- 869 title: "No Inner!" 870 --- 871 {{< noinner >}}{{< /noinner >}} 872 873 874 `).WithTemplatesAdded( 875 "layouts/shortcodes/noinner.html", `No inner here.`) 876 877 err := b.BuildE(BuildCfg{}) 878 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`)) 879 } 880 881 func TestShortcodeStableOutputFormatTemplates(t *testing.T) { 882 t.Parallel() 883 884 for i := 0; i < 5; i++ { 885 886 b := newTestSitesBuilder(t) 887 888 const numPages = 10 889 890 for i := 0; i < numPages; i++ { 891 b.WithContent(fmt.Sprintf("page%d.md", i), `--- 892 title: "Page" 893 outputs: ["html", "css", "csv", "json"] 894 --- 895 {{< myshort >}} 896 897 `) 898 } 899 900 b.WithTemplates( 901 "_default/single.html", "{{ .Content }}", 902 "_default/single.css", "{{ .Content }}", 903 "_default/single.csv", "{{ .Content }}", 904 "_default/single.json", "{{ .Content }}", 905 "shortcodes/myshort.html", `Short-HTML`, 906 "shortcodes/myshort.csv", `Short-CSV`, 907 ) 908 909 b.Build(BuildCfg{}) 910 911 // helpers.PrintFs(b.Fs.Destination, "public", os.Stdout) 912 913 for i := 0; i < numPages; i++ { 914 b.AssertFileContent(fmt.Sprintf("public/page%d/index.html", i), "Short-HTML") 915 b.AssertFileContent(fmt.Sprintf("public/page%d/index.csv", i), "Short-CSV") 916 b.AssertFileContent(fmt.Sprintf("public/page%d/index.json", i), "Short-HTML") 917 918 } 919 920 for i := 0; i < numPages; i++ { 921 b.AssertFileContent(fmt.Sprintf("public/page%d/styles.css", i), "Short-HTML") 922 } 923 924 } 925 } 926 927 // #9821 928 func TestShortcodeMarkdownOutputFormat(t *testing.T) { 929 t.Parallel() 930 931 files := ` 932 -- config.toml -- 933 -- content/p1.md -- 934 --- 935 title: "p1" 936 --- 937 {{< foo >}} 938 -- layouts/shortcodes/foo.md -- 939 §§§ 940 <x 941 §§§ 942 -- layouts/_default/single.html -- 943 {{ .Content }} 944 ` 945 946 b := NewIntegrationTestBuilder( 947 IntegrationTestConfig{ 948 T: t, 949 TxtarString: files, 950 Running: true, 951 }, 952 ).Build() 953 954 b.AssertFileContent("public/p1/index.html", ` 955 <x 956 `) 957 958 } 959 960 func TestShortcodePreserveIndentation(t *testing.T) { 961 t.Parallel() 962 963 files := ` 964 -- config.toml -- 965 -- content/p1.md -- 966 --- 967 title: "p1" 968 --- 969 970 ## List With Indented Shortcodes 971 972 1. List 1 973 {{% mark1 %}} 974 1. Item Mark1 1 975 1. Item Mark1 2 976 {{% mark2 %}} 977 {{% /mark1 %}} 978 -- layouts/shortcodes/mark1.md -- 979 {{ .Inner }} 980 -- layouts/shortcodes/mark2.md -- 981 1. Item Mark2 1 982 1. Item Mark2 2 983 1. Item Mark2 2-1 984 1. Item Mark2 3 985 -- layouts/_default/single.html -- 986 {{ .Content }} 987 ` 988 989 b := NewIntegrationTestBuilder( 990 IntegrationTestConfig{ 991 T: t, 992 TxtarString: files, 993 Running: true, 994 }, 995 ).Build() 996 997 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>") 998 999 } 1000 1001 func TestShortcodeCodeblockIndent(t *testing.T) { 1002 t.Parallel() 1003 1004 files := ` 1005 -- config.toml -- 1006 -- content/p1.md -- 1007 --- 1008 title: "p1" 1009 --- 1010 1011 ## Code block 1012 1013 {{% code %}} 1014 1015 -- layouts/shortcodes/code.md -- 1016 echo "foo"; 1017 -- layouts/_default/single.html -- 1018 {{ .Content }} 1019 ` 1020 1021 b := NewIntegrationTestBuilder( 1022 IntegrationTestConfig{ 1023 T: t, 1024 TxtarString: files, 1025 Running: true, 1026 }, 1027 ).Build() 1028 1029 b.AssertFileContent("public/p1/index.html", "<pre><code>echo "foo";\n</code></pre>") 1030 1031 } 1032 1033 func TestShortcodeHighlightDeindent(t *testing.T) { 1034 t.Parallel() 1035 1036 files := ` 1037 -- config.toml -- 1038 [markup] 1039 [markup.highlight] 1040 codeFences = true 1041 noClasses = false 1042 -- content/p1.md -- 1043 --- 1044 title: "p1" 1045 --- 1046 1047 ## Indent 5 Spaces 1048 1049 {{< highlight bash >}} 1050 line 1; 1051 line 2; 1052 line 3; 1053 {{< /highlight >}} 1054 1055 -- layouts/_default/single.html -- 1056 {{ .Content }} 1057 ` 1058 1059 b := NewIntegrationTestBuilder( 1060 IntegrationTestConfig{ 1061 T: t, 1062 TxtarString: files, 1063 Running: true, 1064 }, 1065 ).Build() 1066 1067 b.AssertFileContent("public/p1/index.html", ` 1068 <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> 1069 </span></span><span class="line"><span class="cl">line 2<span class="p">;</span> 1070 </span></span><span class="line"><span class="cl">line 3<span class="p">;</span></span></span></code></pre></div> 1071 </code></pre> 1072 1073 `) 1074 1075 } 1076 1077 // Issue 10236. 1078 func TestShortcodeParamEscapedQuote(t *testing.T) { 1079 t.Parallel() 1080 1081 files := ` 1082 -- config.toml -- 1083 -- content/p1.md -- 1084 --- 1085 title: "p1" 1086 --- 1087 1088 {{< figure src="/media/spf13.jpg" title="Steve \"Francia\"." >}} 1089 1090 -- layouts/shortcodes/figure.html -- 1091 Title: {{ .Get "title" | safeHTML }} 1092 -- layouts/_default/single.html -- 1093 {{ .Content }} 1094 ` 1095 1096 b := NewIntegrationTestBuilder( 1097 IntegrationTestConfig{ 1098 T: t, 1099 TxtarString: files, 1100 Running: true, 1101 Verbose: true, 1102 }, 1103 ).Build() 1104 1105 b.AssertFileContent("public/p1/index.html", `Title: Steve "Francia".`) 1106 1107 } 1108 1109 // Issue 10391. 1110 func TestNestedShortcodeCustomOutputFormat(t *testing.T) { 1111 t.Parallel() 1112 1113 files := ` 1114 -- config.toml -- 1115 1116 [outputFormats.Foobar] 1117 baseName = "foobar" 1118 isPlainText = true 1119 mediaType = "application/json" 1120 notAlternative = true 1121 1122 [languages.en] 1123 languageName = "English" 1124 1125 [languages.en.outputs] 1126 home = [ "HTML", "RSS", "Foobar" ] 1127 1128 [languages.fr] 1129 languageName = "Français" 1130 1131 [[module.mounts]] 1132 source = "content/en" 1133 target = "content" 1134 lang = "en" 1135 1136 [[module.mounts]] 1137 source = "content/fr" 1138 target = "content" 1139 lang = "fr" 1140 1141 -- layouts/_default/list.foobar.json -- 1142 {{- $.Scratch.Add "data" slice -}} 1143 {{- range (where .Site.AllPages "Kind" "!=" "home") -}} 1144 {{- $.Scratch.Add "data" (dict "content" (.Plain | truncate 10000) "type" .Type "full_url" .Permalink) -}} 1145 {{- end -}} 1146 {{- $.Scratch.Get "data" | jsonify -}} 1147 -- content/en/p1.md -- 1148 --- 1149 title: "p1" 1150 --- 1151 1152 ### More information 1153 1154 {{< tabs >}} 1155 {{% tab "Test" %}} 1156 1157 It's a test 1158 1159 {{% /tab %}} 1160 {{< /tabs >}} 1161 1162 -- content/fr/p2.md -- 1163 --- 1164 title: Test 1165 --- 1166 1167 ### Plus d'informations 1168 1169 {{< tabs >}} 1170 {{% tab "Test" %}} 1171 1172 C'est un test 1173 1174 {{% /tab %}} 1175 {{< /tabs >}} 1176 1177 -- layouts/shortcodes/tabs.html -- 1178 <div> 1179 <div class="tab-content">{{ .Inner }}</div> 1180 </div> 1181 1182 -- layouts/shortcodes/tab.html -- 1183 <div>{{ .Inner }}</div> 1184 1185 -- layouts/_default/single.html -- 1186 {{ .Content }} 1187 ` 1188 1189 b := NewIntegrationTestBuilder( 1190 IntegrationTestConfig{ 1191 T: t, 1192 TxtarString: files, 1193 Running: true, 1194 Verbose: true, 1195 }, 1196 ).Build() 1197 1198 b.AssertFileContent("public/fr/p2/index.html", `plus-dinformations`) 1199 1200 } 1201 1202 // Issue 10671. 1203 func TestShortcodeInnerShouldBeEmptyWhenNotClosed(t *testing.T) { 1204 t.Parallel() 1205 1206 files := ` 1207 -- config.toml -- 1208 disableKinds = ["home", "taxonomy", "term"] 1209 -- content/p1.md -- 1210 --- 1211 title: "p1" 1212 --- 1213 1214 {{< sc "self-closing" />}} 1215 1216 Text. 1217 1218 {{< sc "closing-no-newline" >}}{{< /sc >}} 1219 1220 -- layouts/shortcodes/sc.html -- 1221 Inner: {{ .Get 0 }}: {{ len .Inner }} 1222 InnerDeindent: {{ .Get 0 }}: {{ len .InnerDeindent }} 1223 -- layouts/_default/single.html -- 1224 {{ .Content }} 1225 ` 1226 1227 b := NewIntegrationTestBuilder( 1228 IntegrationTestConfig{ 1229 T: t, 1230 TxtarString: files, 1231 Running: true, 1232 Verbose: true, 1233 }, 1234 ).Build() 1235 1236 b.AssertFileContent("public/p1/index.html", ` 1237 Inner: self-closing: 0 1238 InnerDeindent: self-closing: 0 1239 Inner: closing-no-newline: 0 1240 InnerDeindent: closing-no-newline: 0 1241 1242 `) 1243 } 1244 1245 // Issue 10675. 1246 func TestShortcodeErrorWhenItShouldBeClosed(t *testing.T) { 1247 t.Parallel() 1248 1249 files := ` 1250 -- config.toml -- 1251 disableKinds = ["home", "taxonomy", "term"] 1252 -- content/p1.md -- 1253 --- 1254 title: "p1" 1255 --- 1256 1257 {{< sc >}} 1258 1259 Text. 1260 1261 -- layouts/shortcodes/sc.html -- 1262 Inner: {{ .Get 0 }}: {{ len .Inner }} 1263 -- layouts/_default/single.html -- 1264 {{ .Content }} 1265 ` 1266 1267 b, err := NewIntegrationTestBuilder( 1268 IntegrationTestConfig{ 1269 T: t, 1270 TxtarString: files, 1271 Running: true, 1272 Verbose: true, 1273 }, 1274 ).BuildE() 1275 1276 b.Assert(err, qt.Not(qt.IsNil)) 1277 b.Assert(err.Error(), qt.Contains, `p1.md:5:1": failed to extract shortcode: unclosed shortcode "sc"`) 1278 }