hugo

Fork of github.com/gohugoio/hugo with reverse pagination support

git clone git://git.shimmy1996.com/hugo.git

shortcode_test.go (31283B)

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