github.com/lyeb/hugo@v0.47.1/hugolib/shortcode_test.go (about)

     1  // Copyright 2016 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  	"regexp"
    21  	"sort"
    22  	"strings"
    23  	"testing"
    24  
    25  	"github.com/spf13/viper"
    26  
    27  	jww "github.com/spf13/jwalterweatherman"
    28  
    29  	"github.com/spf13/afero"
    30  
    31  	"github.com/gohugoio/hugo/output"
    32  
    33  	"github.com/gohugoio/hugo/media"
    34  
    35  	"github.com/gohugoio/hugo/deps"
    36  	"github.com/gohugoio/hugo/helpers"
    37  	"github.com/gohugoio/hugo/tpl"
    38  
    39  	"github.com/stretchr/testify/require"
    40  )
    41  
    42  // TODO(bep) remove
    43  func pageFromString(in, filename string, withTemplate ...func(templ tpl.TemplateHandler) error) (*Page, error) {
    44  	var err error
    45  	cfg, fs := newTestCfg()
    46  
    47  	d := deps.DepsCfg{Cfg: cfg, Fs: fs, WithTemplate: withTemplate[0]}
    48  
    49  	s, err := NewSiteForCfg(d)
    50  	if err != nil {
    51  		return nil, err
    52  	}
    53  
    54  	return s.NewPageFrom(strings.NewReader(in), filename)
    55  }
    56  
    57  func CheckShortCodeMatch(t *testing.T, input, expected string, withTemplate func(templ tpl.TemplateHandler) error) {
    58  	CheckShortCodeMatchAndError(t, input, expected, withTemplate, false)
    59  }
    60  
    61  func CheckShortCodeMatchAndError(t *testing.T, input, expected string, withTemplate func(templ tpl.TemplateHandler) error, expectError bool) {
    62  
    63  	cfg, fs := newTestCfg()
    64  
    65  	// Need some front matter, see https://github.com/gohugoio/hugo/issues/2337
    66  	contentFile := `---
    67  title: "Title"
    68  ---
    69  ` + input
    70  
    71  	writeSource(t, fs, "content/simple.md", contentFile)
    72  
    73  	h, err := NewHugoSites(deps.DepsCfg{Fs: fs, Cfg: cfg, WithTemplate: withTemplate})
    74  
    75  	require.NoError(t, err)
    76  	require.Len(t, h.Sites, 1)
    77  
    78  	err = h.Build(BuildCfg{})
    79  
    80  	if err != nil && !expectError {
    81  		t.Fatalf("Shortcode rendered error %s.", err)
    82  	}
    83  
    84  	if err == nil && expectError {
    85  		t.Fatalf("No error from shortcode")
    86  	}
    87  
    88  	require.Len(t, h.Sites[0].RegularPages, 1)
    89  
    90  	output := strings.TrimSpace(string(h.Sites[0].RegularPages[0].content()))
    91  	output = strings.TrimPrefix(output, "<p>")
    92  	output = strings.TrimSuffix(output, "</p>")
    93  
    94  	expected = strings.TrimSpace(expected)
    95  
    96  	if output != expected {
    97  		t.Fatalf("Shortcode render didn't match. got \n%q but expected \n%q", output, expected)
    98  	}
    99  }
   100  
   101  func TestNonSC(t *testing.T) {
   102  	t.Parallel()
   103  	// notice the syntax diff from 0.12, now comment delims must be added
   104  	CheckShortCodeMatch(t, "{{%/* movie 47238zzb */%}}", "{{% movie 47238zzb %}}", nil)
   105  }
   106  
   107  // Issue #929
   108  func TestHyphenatedSC(t *testing.T) {
   109  	t.Parallel()
   110  	wt := func(tem tpl.TemplateHandler) error {
   111  
   112  		tem.AddTemplate("_internal/shortcodes/hyphenated-video.html", `Playing Video {{ .Get 0 }}`)
   113  		return nil
   114  	}
   115  
   116  	CheckShortCodeMatch(t, "{{< hyphenated-video 47238zzb >}}", "Playing Video 47238zzb", wt)
   117  }
   118  
   119  // Issue #1753
   120  func TestNoTrailingNewline(t *testing.T) {
   121  	t.Parallel()
   122  	wt := func(tem tpl.TemplateHandler) error {
   123  		tem.AddTemplate("_internal/shortcodes/a.html", `{{ .Get 0 }}`)
   124  		return nil
   125  	}
   126  
   127  	CheckShortCodeMatch(t, "ab{{< a c >}}d", "abcd", wt)
   128  }
   129  
   130  func TestPositionalParamSC(t *testing.T) {
   131  	t.Parallel()
   132  	wt := func(tem tpl.TemplateHandler) error {
   133  		tem.AddTemplate("_internal/shortcodes/video.html", `Playing Video {{ .Get 0 }}`)
   134  		return nil
   135  	}
   136  
   137  	CheckShortCodeMatch(t, "{{< video 47238zzb >}}", "Playing Video 47238zzb", wt)
   138  	CheckShortCodeMatch(t, "{{< video 47238zzb 132 >}}", "Playing Video 47238zzb", wt)
   139  	CheckShortCodeMatch(t, "{{<video 47238zzb>}}", "Playing Video 47238zzb", wt)
   140  	CheckShortCodeMatch(t, "{{<video 47238zzb    >}}", "Playing Video 47238zzb", wt)
   141  	CheckShortCodeMatch(t, "{{<   video   47238zzb    >}}", "Playing Video 47238zzb", wt)
   142  }
   143  
   144  func TestPositionalParamIndexOutOfBounds(t *testing.T) {
   145  	t.Parallel()
   146  	wt := func(tem tpl.TemplateHandler) error {
   147  		tem.AddTemplate("_internal/shortcodes/video.html", `Playing Video {{ with .Get 1 }}{{ . }}{{ else }}Missing{{ end }}`)
   148  		return nil
   149  	}
   150  	CheckShortCodeMatch(t, "{{< video 47238zzb >}}", "Playing Video Missing", wt)
   151  }
   152  
   153  // #5071
   154  func TestShortcodeRelated(t *testing.T) {
   155  	t.Parallel()
   156  	wt := func(tem tpl.TemplateHandler) error {
   157  		tem.AddTemplate("_internal/shortcodes/a.html", `{{ len (.Site.RegularPages.Related .Page) }}`)
   158  		return nil
   159  	}
   160  
   161  	CheckShortCodeMatch(t, "{{< a >}}", "0", wt)
   162  }
   163  
   164  // some repro issues for panics in Go Fuzz testing
   165  
   166  func TestNamedParamSC(t *testing.T) {
   167  	t.Parallel()
   168  	wt := func(tem tpl.TemplateHandler) error {
   169  		tem.AddTemplate("_internal/shortcodes/img.html", `<img{{ with .Get "src" }} src="{{.}}"{{end}}{{with .Get "class"}} class="{{.}}"{{end}}>`)
   170  		return nil
   171  	}
   172  	CheckShortCodeMatch(t, `{{< img src="one" >}}`, `<img src="one">`, wt)
   173  	CheckShortCodeMatch(t, `{{< img class="aspen" >}}`, `<img class="aspen">`, wt)
   174  	CheckShortCodeMatch(t, `{{< img src= "one" >}}`, `<img src="one">`, wt)
   175  	CheckShortCodeMatch(t, `{{< img src ="one" >}}`, `<img src="one">`, wt)
   176  	CheckShortCodeMatch(t, `{{< img src = "one" >}}`, `<img src="one">`, wt)
   177  	CheckShortCodeMatch(t, `{{< img src = "one" class = "aspen grove" >}}`, `<img src="one" class="aspen grove">`, wt)
   178  }
   179  
   180  // Issue #2294
   181  func TestNestedNamedMissingParam(t *testing.T) {
   182  	t.Parallel()
   183  	wt := func(tem tpl.TemplateHandler) error {
   184  		tem.AddTemplate("_internal/shortcodes/acc.html", `<div class="acc">{{ .Inner }}</div>`)
   185  		tem.AddTemplate("_internal/shortcodes/div.html", `<div {{with .Get "class"}} class="{{ . }}"{{ end }}>{{ .Inner }}</div>`)
   186  		tem.AddTemplate("_internal/shortcodes/div2.html", `<div {{with .Get 0}} class="{{ . }}"{{ end }}>{{ .Inner }}</div>`)
   187  		return nil
   188  	}
   189  	CheckShortCodeMatch(t,
   190  		`{{% acc %}}{{% div %}}d1{{% /div %}}{{% div2 %}}d2{{% /div2 %}}{{% /acc %}}`,
   191  		"<div class=\"acc\"><div >d1</div><div >d2</div>\n</div>", wt)
   192  }
   193  
   194  func TestIsNamedParamsSC(t *testing.T) {
   195  	t.Parallel()
   196  	wt := func(tem tpl.TemplateHandler) error {
   197  		tem.AddTemplate("_internal/shortcodes/bynameorposition.html", `{{ with .Get "id" }}Named: {{ . }}{{ else }}Pos: {{ .Get 0 }}{{ end }}`)
   198  		tem.AddTemplate("_internal/shortcodes/ifnamedparams.html", `<div id="{{ if .IsNamedParams }}{{ .Get "id" }}{{ else }}{{ .Get 0 }}{{end}}">`)
   199  		return nil
   200  	}
   201  	CheckShortCodeMatch(t, `{{< ifnamedparams id="name" >}}`, `<div id="name">`, wt)
   202  	CheckShortCodeMatch(t, `{{< ifnamedparams position >}}`, `<div id="position">`, wt)
   203  	CheckShortCodeMatch(t, `{{< bynameorposition id="name" >}}`, `Named: name`, wt)
   204  	CheckShortCodeMatch(t, `{{< bynameorposition position >}}`, `Pos: position`, wt)
   205  }
   206  
   207  func TestInnerSC(t *testing.T) {
   208  	t.Parallel()
   209  	wt := func(tem tpl.TemplateHandler) error {
   210  		tem.AddTemplate("_internal/shortcodes/inside.html", `<div{{with .Get "class"}} class="{{.}}"{{end}}>{{ .Inner }}</div>`)
   211  		return nil
   212  	}
   213  	CheckShortCodeMatch(t, `{{< inside class="aspen" >}}`, `<div class="aspen"></div>`, wt)
   214  	CheckShortCodeMatch(t, `{{< inside class="aspen" >}}More Here{{< /inside >}}`, "<div class=\"aspen\">More Here</div>", wt)
   215  	CheckShortCodeMatch(t, `{{< inside >}}More Here{{< /inside >}}`, "<div>More Here</div>", wt)
   216  }
   217  
   218  func TestInnerSCWithMarkdown(t *testing.T) {
   219  	t.Parallel()
   220  	wt := func(tem tpl.TemplateHandler) error {
   221  		tem.AddTemplate("_internal/shortcodes/inside.html", `<div{{with .Get "class"}} class="{{.}}"{{end}}>{{ .Inner }}</div>`)
   222  		return nil
   223  	}
   224  	CheckShortCodeMatch(t, `{{% inside %}}
   225  # More Here
   226  
   227  [link](http://spf13.com) and text
   228  
   229  {{% /inside %}}`, "<div><h1 id=\"more-here\">More Here</h1>\n\n<p><a href=\"http://spf13.com\">link</a> and text</p>\n</div>", wt)
   230  }
   231  
   232  func TestInnerSCWithAndWithoutMarkdown(t *testing.T) {
   233  	t.Parallel()
   234  	wt := func(tem tpl.TemplateHandler) error {
   235  		tem.AddTemplate("_internal/shortcodes/inside.html", `<div{{with .Get "class"}} class="{{.}}"{{end}}>{{ .Inner }}</div>`)
   236  		return nil
   237  	}
   238  	CheckShortCodeMatch(t, `{{% inside %}}
   239  # More Here
   240  
   241  [link](http://spf13.com) and text
   242  
   243  {{% /inside %}}
   244  
   245  And then:
   246  
   247  {{< inside >}}
   248  # More Here
   249  
   250  This is **plain** text.
   251  
   252  {{< /inside >}}
   253  `, "<div><h1 id=\"more-here\">More Here</h1>\n\n<p><a href=\"http://spf13.com\">link</a> and text</p>\n</div>\n\n<p>And then:</p>\n\n<div>\n# More Here\n\nThis is **plain** text.\n\n</div>", wt)
   254  }
   255  
   256  func TestEmbeddedSC(t *testing.T) {
   257  	t.Parallel()
   258  	CheckShortCodeMatch(t, `{{% figure src="/found/here" class="bananas orange" %}}`, "\n<figure class=\"bananas orange\">\n    \n        <img src=\"/found/here\" />\n    \n    \n</figure>\n", nil)
   259  	CheckShortCodeMatch(t, `{{% figure src="/found/here" class="bananas orange" caption="This is a caption" %}}`, "\n<figure class=\"bananas orange\">\n    \n        <img src=\"/found/here\" alt=\"This is a caption\" />\n    \n    \n    <figcaption>\n        <p>\n        This is a caption\n        \n            \n        \n        </p> \n    </figcaption>\n    \n</figure>\n", nil)
   260  }
   261  
   262  func TestNestedSC(t *testing.T) {
   263  	t.Parallel()
   264  	wt := func(tem tpl.TemplateHandler) error {
   265  		tem.AddTemplate("_internal/shortcodes/scn1.html", `<div>Outer, inner is {{ .Inner }}</div>`)
   266  		tem.AddTemplate("_internal/shortcodes/scn2.html", `<div>SC2</div>`)
   267  		return nil
   268  	}
   269  	CheckShortCodeMatch(t, `{{% scn1 %}}{{% scn2 %}}{{% /scn1 %}}`, "<div>Outer, inner is <div>SC2</div>\n</div>", wt)
   270  
   271  	CheckShortCodeMatch(t, `{{< scn1 >}}{{% scn2 %}}{{< /scn1 >}}`, "<div>Outer, inner is <div>SC2</div></div>", wt)
   272  }
   273  
   274  func TestNestedComplexSC(t *testing.T) {
   275  	t.Parallel()
   276  	wt := func(tem tpl.TemplateHandler) error {
   277  		tem.AddTemplate("_internal/shortcodes/row.html", `-row-{{ .Inner}}-rowStop-`)
   278  		tem.AddTemplate("_internal/shortcodes/column.html", `-col-{{.Inner    }}-colStop-`)
   279  		tem.AddTemplate("_internal/shortcodes/aside.html", `-aside-{{    .Inner  }}-asideStop-`)
   280  		return nil
   281  	}
   282  	CheckShortCodeMatch(t, `{{< row >}}1-s{{% column %}}2-**s**{{< aside >}}3-**s**{{< /aside >}}4-s{{% /column %}}5-s{{< /row >}}6-s`,
   283  		"-row-1-s-col-2-<strong>s</strong>-aside-3-<strong>s</strong>-asideStop-4-s-colStop-5-s-rowStop-6-s", wt)
   284  
   285  	// turn around the markup flag
   286  	CheckShortCodeMatch(t, `{{% row %}}1-s{{< column >}}2-**s**{{% aside %}}3-**s**{{% /aside %}}4-s{{< /column >}}5-s{{% /row %}}6-s`,
   287  		"-row-1-s-col-2-<strong>s</strong>-aside-3-<strong>s</strong>-asideStop-4-s-colStop-5-s-rowStop-6-s", wt)
   288  }
   289  
   290  func TestParentShortcode(t *testing.T) {
   291  	t.Parallel()
   292  	wt := func(tem tpl.TemplateHandler) error {
   293  		tem.AddTemplate("_internal/shortcodes/r1.html", `1: {{ .Get "pr1" }} {{ .Inner }}`)
   294  		tem.AddTemplate("_internal/shortcodes/r2.html", `2: {{ .Parent.Get "pr1" }}{{ .Get "pr2" }} {{ .Inner }}`)
   295  		tem.AddTemplate("_internal/shortcodes/r3.html", `3: {{ .Parent.Parent.Get "pr1" }}{{ .Parent.Get "pr2" }}{{ .Get "pr3" }} {{ .Inner }}`)
   296  		return nil
   297  	}
   298  	CheckShortCodeMatch(t, `{{< r1 pr1="p1" >}}1: {{< r2 pr2="p2" >}}2: {{< r3 pr3="p3" >}}{{< /r3 >}}{{< /r2 >}}{{< /r1 >}}`,
   299  		"1: p1 1: 2: p1p2 2: 3: p1p2p3 ", wt)
   300  
   301  }
   302  
   303  func TestFigureOnlySrc(t *testing.T) {
   304  	t.Parallel()
   305  	CheckShortCodeMatch(t, `{{< figure src="/found/here" >}}`, "\n<figure>\n    \n        <img src=\"/found/here\" />\n    \n    \n</figure>\n", nil)
   306  }
   307  
   308  func TestFigureImgWidth(t *testing.T) {
   309  	t.Parallel()
   310  	CheckShortCodeMatch(t, `{{% figure src="/found/here" class="bananas orange" alt="apple" width="100px" %}}`, "\n<figure class=\"bananas orange\">\n    \n        <img src=\"/found/here\" alt=\"apple\" width=\"100px\" />\n    \n    \n</figure>\n", nil)
   311  }
   312  
   313  func TestFigureImgHeight(t *testing.T) {
   314  	t.Parallel()
   315  	CheckShortCodeMatch(t, `{{% figure src="/found/here" class="bananas orange" alt="apple" height="100px" %}}`, "\n<figure class=\"bananas orange\">\n    \n        <img src=\"/found/here\" alt=\"apple\" height=\"100px\" />\n    \n    \n</figure>\n", nil)
   316  }
   317  
   318  func TestFigureImgWidthAndHeight(t *testing.T) {
   319  	t.Parallel()
   320  	CheckShortCodeMatch(t, `{{% figure src="/found/here" class="bananas orange" alt="apple" width="50" height="100" %}}`, "\n<figure class=\"bananas orange\">\n    \n        <img src=\"/found/here\" alt=\"apple\" width=\"50\" height=\"100\" />\n    \n    \n</figure>\n", nil)
   321  }
   322  
   323  func TestFigureLinkNoTarget(t *testing.T) {
   324  	t.Parallel()
   325  	CheckShortCodeMatch(t, `{{< figure src="/found/here" link="/jump/here/on/clicking" >}}`, "\n<figure>\n    <a href=\"/jump/here/on/clicking\">\n        <img src=\"/found/here\" />\n    </a>\n    \n</figure>\n", nil)
   326  }
   327  
   328  func TestFigureLinkWithTarget(t *testing.T) {
   329  	t.Parallel()
   330  	CheckShortCodeMatch(t, `{{< figure src="/found/here" link="/jump/here/on/clicking" target="_self" >}}`, "\n<figure>\n    <a href=\"/jump/here/on/clicking\" target=\"_self\">\n        <img src=\"/found/here\" />\n    </a>\n    \n</figure>\n", nil)
   331  }
   332  
   333  func TestFigureLinkWithTargetAndRel(t *testing.T) {
   334  	t.Parallel()
   335  	CheckShortCodeMatch(t, `{{< figure src="/found/here" link="/jump/here/on/clicking" target="_blank" rel="noopener" >}}`, "\n<figure>\n    <a href=\"/jump/here/on/clicking\" target=\"_blank\" rel=\"noopener\">\n        <img src=\"/found/here\" />\n    </a>\n    \n</figure>\n", nil)
   336  }
   337  
   338  // #1642
   339  func TestShortcodeWrappedInPIssue(t *testing.T) {
   340  	t.Parallel()
   341  	wt := func(tem tpl.TemplateHandler) error {
   342  		tem.AddTemplate("_internal/shortcodes/bug.html", `xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx`)
   343  		return nil
   344  	}
   345  	CheckShortCodeMatch(t, `
   346  {{< bug >}}
   347  
   348  {{< bug >}}
   349  `, "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n\nxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", wt)
   350  }
   351  
   352  const testScPlaceholderRegexp = "HAHAHUGOSHORTCODE-\\d+HBHB"
   353  
   354  func TestExtractShortcodes(t *testing.T) {
   355  	t.Parallel()
   356  	for i, this := range []struct {
   357  		name             string
   358  		input            string
   359  		expectShortCodes string
   360  		expect           interface{}
   361  		expectErrorMsg   string
   362  	}{
   363  		{"text", "Some text.", "map[]", "Some text.", ""},
   364  		{"invalid right delim", "{{< tag }}", "", false, "simple.md:4:.*unrecognized character.*}"},
   365  		{"invalid close", "\n{{< /tag >}}", "", false, "simple.md:5:.*got closing shortcode, but none is open"},
   366  		{"invalid close2", "\n\n{{< tag >}}{{< /anotherTag >}}", "", false, "simple.md:6: closing tag for shortcode 'anotherTag' does not match start tag"},
   367  		{"unterminated quote 1", `{{< figure src="im caption="S" >}}`, "", false, "simple.md:4:.got pos.*"},
   368  		{"unterminated quote 1", `{{< figure src="im" caption="S >}}`, "", false, "simple.md:4:.*unterm.*}"},
   369  		{"one shortcode, no markup", "{{< tag >}}", "", testScPlaceholderRegexp, ""},
   370  		{"one shortcode, markup", "{{% tag %}}", "", testScPlaceholderRegexp, ""},
   371  		{"one pos param", "{{% tag param1 %}}", `tag([\"param1\"], true){[]}"]`, testScPlaceholderRegexp, ""},
   372  		{"two pos params", "{{< tag param1 param2>}}", `tag([\"param1\" \"param2\"], false){[]}"]`, testScPlaceholderRegexp, ""},
   373  		{"one named param", `{{% tag param1="value" %}}`, `tag([\"param1:value\"], true){[]}`, testScPlaceholderRegexp, ""},
   374  		{"two named params", `{{< tag param1="value1" param2="value2" >}}`, `tag([\"param1:value1\" \"param2:value2\"], false){[]}"]`,
   375  			testScPlaceholderRegexp, ""},
   376  		{"inner", `Some text. {{< inner >}}Inner Content{{< / inner >}}. Some more text.`, `inner([], false){[Inner Content]}`,
   377  			fmt.Sprintf("Some text. %s. Some more text.", testScPlaceholderRegexp), ""},
   378  		// issue #934
   379  		{"inner self-closing", `Some text. {{< inner />}}. Some more text.`, `inner([], false){[]}`,
   380  			fmt.Sprintf("Some text. %s. Some more text.", testScPlaceholderRegexp), ""},
   381  		{"close, but not inner", "{{< tag >}}foo{{< /tag >}}", "", false, "Shortcode 'tag' in page 'simple.md' has no .Inner.*"},
   382  		{"nested inner", `Inner->{{< inner >}}Inner Content->{{% inner2 param1 %}}inner2txt{{% /inner2 %}}Inner close->{{< / inner >}}<-done`,
   383  			`inner([], false){[Inner Content-> inner2([\"param1\"], true){[inner2txt]} Inner close->]}`,
   384  			fmt.Sprintf("Inner->%s<-done", testScPlaceholderRegexp), ""},
   385  		{"nested, nested inner", `Inner->{{< inner >}}inner2->{{% inner2 param1 %}}inner2txt->inner3{{< inner3>}}inner3txt{{</ inner3 >}}{{% /inner2 %}}final close->{{< / inner >}}<-done`,
   386  			`inner([], false){[inner2-> inner2([\"param1\"], true){[inner2txt->inner3 inner3(%!q(<nil>), false){[inner3txt]}]} final close->`,
   387  			fmt.Sprintf("Inner->%s<-done", testScPlaceholderRegexp), ""},
   388  		{"two inner", `Some text. {{% inner %}}First **Inner** Content{{% / inner %}} {{< inner >}}Inner **Content**{{< / inner >}}. Some more text.`,
   389  			`map["HAHAHUGOSHORTCODE-1HBHB:inner([], true){[First **Inner** Content]}" "HAHAHUGOSHORTCODE-2HBHB:inner([], false){[Inner **Content**]}"]`,
   390  			fmt.Sprintf("Some text. %s %s. Some more text.", testScPlaceholderRegexp, testScPlaceholderRegexp), ""},
   391  		{"closed without content", `Some text. {{< inner param1 >}}{{< / inner >}}. Some more text.`, `inner([\"param1\"], false){[]}`,
   392  			fmt.Sprintf("Some text. %s. Some more text.", testScPlaceholderRegexp), ""},
   393  		{"two shortcodes", "{{< sc1 >}}{{< sc2 >}}",
   394  			`map["HAHAHUGOSHORTCODE-1HBHB:sc1([], false){[]}" "HAHAHUGOSHORTCODE-2HBHB:sc2([], false){[]}"]`,
   395  			testScPlaceholderRegexp + testScPlaceholderRegexp, ""},
   396  		{"mix of shortcodes", `Hello {{< sc1 >}}world{{% sc2 p2="2"%}}. And that's it.`,
   397  			`map["HAHAHUGOSHORTCODE-1HBHB:sc1([], false){[]}" "HAHAHUGOSHORTCODE-2HBHB:sc2([\"p2:2\"]`,
   398  			fmt.Sprintf("Hello %sworld%s. And that's it.", testScPlaceholderRegexp, testScPlaceholderRegexp), ""},
   399  		{"mix with inner", `Hello {{< sc1 >}}world{{% inner p2="2"%}}Inner{{%/ inner %}}. And that's it.`,
   400  			`map["HAHAHUGOSHORTCODE-1HBHB:sc1([], false){[]}" "HAHAHUGOSHORTCODE-2HBHB:inner([\"p2:2\"], true){[Inner]}"]`,
   401  			fmt.Sprintf("Hello %sworld%s. And that's it.", testScPlaceholderRegexp, testScPlaceholderRegexp), ""},
   402  	} {
   403  
   404  		p, _ := pageFromString(simplePage, "simple.md", func(templ tpl.TemplateHandler) error {
   405  			templ.AddTemplate("_internal/shortcodes/tag.html", `tag`)
   406  			templ.AddTemplate("_internal/shortcodes/sc1.html", `sc1`)
   407  			templ.AddTemplate("_internal/shortcodes/sc2.html", `sc2`)
   408  			templ.AddTemplate("_internal/shortcodes/inner.html", `{{with .Inner }}{{ . }}{{ end }}`)
   409  			templ.AddTemplate("_internal/shortcodes/inner2.html", `{{.Inner}}`)
   410  			templ.AddTemplate("_internal/shortcodes/inner3.html", `{{.Inner}}`)
   411  			return nil
   412  		})
   413  
   414  		counter := 0
   415  
   416  		s := newShortcodeHandler(p)
   417  
   418  		s.placeholderFunc = func() string {
   419  			counter++
   420  			return fmt.Sprintf("HAHA%s-%dHBHB", shortcodePlaceholderPrefix, counter)
   421  		}
   422  
   423  		content, err := s.extractShortcodes(this.input, p.withoutContent())
   424  
   425  		if b, ok := this.expect.(bool); ok && !b {
   426  			if err == nil {
   427  				t.Fatalf("[%d] %s: ExtractShortcodes didn't return an expected error", i, this.name)
   428  			} else {
   429  				r, _ := regexp.Compile(this.expectErrorMsg)
   430  				if !r.MatchString(err.Error()) {
   431  					t.Fatalf("[%d] %s: ExtractShortcodes didn't return an expected error message, got %s but expected %s",
   432  						i, this.name, err.Error(), this.expectErrorMsg)
   433  				}
   434  			}
   435  			continue
   436  		} else {
   437  			if err != nil {
   438  				t.Fatalf("[%d] %s: failed: %q", i, this.name, err)
   439  			}
   440  		}
   441  
   442  		shortCodes := s.shortcodes
   443  
   444  		var expected string
   445  		av := reflect.ValueOf(this.expect)
   446  		switch av.Kind() {
   447  		case reflect.String:
   448  			expected = av.String()
   449  		}
   450  
   451  		r, err := regexp.Compile(expected)
   452  
   453  		if err != nil {
   454  			t.Fatalf("[%d] %s: Failed to compile regexp %q: %q", i, this.name, expected, err)
   455  		}
   456  
   457  		if strings.Count(content, shortcodePlaceholderPrefix) != shortCodes.Len() {
   458  			t.Fatalf("[%d] %s: Not enough placeholders, found %d", i, this.name, shortCodes.Len())
   459  		}
   460  
   461  		if !r.MatchString(content) {
   462  			t.Fatalf("[%d] %s: Shortcode extract didn't match. got %q but expected %q", i, this.name, content, expected)
   463  		}
   464  
   465  		for _, placeHolder := range shortCodes.Keys() {
   466  			sc := shortCodes.getShortcode(placeHolder)
   467  			if !strings.Contains(content, placeHolder.(string)) {
   468  				t.Fatalf("[%d] %s: Output does not contain placeholder %q", i, this.name, placeHolder)
   469  			}
   470  
   471  			if sc.params == nil {
   472  				t.Fatalf("[%d] %s: Params is nil for shortcode '%s'", i, this.name, sc.name)
   473  			}
   474  		}
   475  
   476  		if this.expectShortCodes != "" {
   477  			shortCodesAsStr := fmt.Sprintf("map%q", collectAndSortShortcodes(shortCodes))
   478  			if !strings.Contains(shortCodesAsStr, this.expectShortCodes) {
   479  				t.Fatalf("[%d] %s: Shortcodes not as expected, got\n%s but expected\n%s", i, this.name, shortCodesAsStr, this.expectShortCodes)
   480  			}
   481  		}
   482  	}
   483  }
   484  
   485  func TestShortcodesInSite(t *testing.T) {
   486  	t.Parallel()
   487  	baseURL := "http://foo/bar"
   488  
   489  	tests := []struct {
   490  		contentPath string
   491  		content     string
   492  		outFile     string
   493  		expected    string
   494  	}{
   495  		{"sect/doc1.md", `a{{< b >}}c`,
   496  			filepath.FromSlash("public/sect/doc1/index.html"), "<p>abc</p>\n"},
   497  		// Issue #1642: Multiple shortcodes wrapped in P
   498  		// Deliberately forced to pass even if they maybe shouldn't.
   499  		{"sect/doc2.md", `a
   500  
   501  {{< b >}}		
   502  {{< c >}}
   503  {{< d >}}
   504  
   505  e`,
   506  			filepath.FromSlash("public/sect/doc2/index.html"),
   507  			"<p>a</p>\n\n<p>b<br />\nc\nd</p>\n\n<p>e</p>\n"},
   508  		{"sect/doc3.md", `a
   509  
   510  {{< b >}}		
   511  {{< c >}}
   512  
   513  {{< d >}}
   514  
   515  e`,
   516  			filepath.FromSlash("public/sect/doc3/index.html"),
   517  			"<p>a</p>\n\n<p>b<br />\nc</p>\n\nd\n\n<p>e</p>\n"},
   518  		{"sect/doc4.md", `a
   519  {{< b >}}
   520  {{< b >}}
   521  {{< b >}}
   522  {{< b >}}
   523  {{< b >}}
   524  
   525  
   526  
   527  
   528  
   529  
   530  
   531  
   532  
   533  
   534  `,
   535  			filepath.FromSlash("public/sect/doc4/index.html"),
   536  			"<p>a\nb\nb\nb\nb\nb</p>\n"},
   537  		// #2192 #2209: Shortcodes in markdown headers
   538  		{"sect/doc5.md", `# {{< b >}}	
   539  ## {{% c %}}`,
   540  			filepath.FromSlash("public/sect/doc5/index.html"), "\n\n<h1 id=\"hahahugoshortcode-1hbhb\">b</h1>\n\n<h2 id=\"hahahugoshortcode-2hbhb\">c</h2>\n"},
   541  		// #2223 pygments
   542  		{"sect/doc6.md", "\n```bash\nb = {{< b >}} c = {{% c %}}\n```\n",
   543  			filepath.FromSlash("public/sect/doc6/index.html"),
   544  			`<span class="nv">b</span>`},
   545  		// #2249
   546  		{"sect/doc7.ad", `_Shortcodes:_ *b: {{< b >}} c: {{% c %}}*`,
   547  			filepath.FromSlash("public/sect/doc7/index.html"),
   548  			"<div class=\"paragraph\">\n<p><em>Shortcodes:</em> <strong>b: b c: c</strong></p>\n</div>\n"},
   549  		{"sect/doc8.rst", `**Shortcodes:** *b: {{< b >}} c: {{% c %}}*`,
   550  			filepath.FromSlash("public/sect/doc8/index.html"),
   551  			"<div class=\"document\">\n\n\n<p><strong>Shortcodes:</strong> <em>b: b c: c</em></p>\n</div>"},
   552  		{"sect/doc9.mmark", `
   553  ---
   554  menu:
   555    main:
   556      parent: 'parent'
   557  ---
   558  **Shortcodes:** *b: {{< b >}} c: {{% c %}}*`,
   559  			filepath.FromSlash("public/sect/doc9/index.html"),
   560  			"<p><strong>Shortcodes:</strong> <em>b: b c: c</em></p>\n"},
   561  		// Issue #1229: Menus not available in shortcode.
   562  		{"sect/doc10.md", `---
   563  menu:
   564    main:
   565      identifier: 'parent'
   566  tags:
   567  - Menu
   568  ---
   569  **Menus:** {{< menu >}}`,
   570  			filepath.FromSlash("public/sect/doc10/index.html"),
   571  			"<p><strong>Menus:</strong> 1</p>\n"},
   572  		// Issue #2323: Taxonomies not available in shortcode.
   573  		{"sect/doc11.md", `---
   574  tags:
   575  - Bugs
   576  ---
   577  **Tags:** {{< tags >}}`,
   578  			filepath.FromSlash("public/sect/doc11/index.html"),
   579  			"<p><strong>Tags:</strong> 2</p>\n"},
   580  	}
   581  
   582  	sources := make([][2]string, len(tests))
   583  
   584  	for i, test := range tests {
   585  		sources[i] = [2]string{filepath.FromSlash(test.contentPath), test.content}
   586  	}
   587  
   588  	addTemplates := func(templ tpl.TemplateHandler) error {
   589  		templ.AddTemplate("_default/single.html", "{{.Content}}")
   590  
   591  		templ.AddTemplate("_internal/shortcodes/b.html", `b`)
   592  		templ.AddTemplate("_internal/shortcodes/c.html", `c`)
   593  		templ.AddTemplate("_internal/shortcodes/d.html", `d`)
   594  		templ.AddTemplate("_internal/shortcodes/menu.html", `{{ len (index .Page.Menus "main").Children }}`)
   595  		templ.AddTemplate("_internal/shortcodes/tags.html", `{{ len .Page.Site.Taxonomies.tags }}`)
   596  
   597  		return nil
   598  
   599  	}
   600  
   601  	cfg, fs := newTestCfg()
   602  
   603  	cfg.Set("defaultContentLanguage", "en")
   604  	cfg.Set("baseURL", baseURL)
   605  	cfg.Set("uglyURLs", false)
   606  	cfg.Set("verbose", true)
   607  
   608  	cfg.Set("pygmentsUseClasses", true)
   609  	cfg.Set("pygmentsCodefences", true)
   610  
   611  	writeSourcesToSource(t, "content", fs, sources...)
   612  
   613  	s := buildSingleSite(t, deps.DepsCfg{WithTemplate: addTemplates, Fs: fs, Cfg: cfg}, BuildCfg{})
   614  	th := testHelper{s.Cfg, s.Fs, t}
   615  
   616  	for _, test := range tests {
   617  		if strings.HasSuffix(test.contentPath, ".ad") && !helpers.HasAsciidoc() {
   618  			fmt.Println("Skip Asciidoc test case as no Asciidoc present.")
   619  			continue
   620  		} else if strings.HasSuffix(test.contentPath, ".rst") && !helpers.HasRst() {
   621  			fmt.Println("Skip Rst test case as no rst2html present.")
   622  			continue
   623  		} else if strings.Contains(test.expected, "code") {
   624  			fmt.Println("Skip Pygments test case as no pygments present.")
   625  			continue
   626  		}
   627  
   628  		th.assertFileContent(test.outFile, test.expected)
   629  	}
   630  
   631  }
   632  
   633  func TestShortcodeMultipleOutputFormats(t *testing.T) {
   634  	t.Parallel()
   635  
   636  	siteConfig := `
   637  baseURL = "http://example.com/blog"
   638  
   639  paginate = 1
   640  
   641  disableKinds = ["section", "taxonomy", "taxonomyTerm", "RSS", "sitemap", "robotsTXT", "404"]
   642  
   643  [outputs]
   644  home = [ "HTML", "AMP", "Calendar" ]
   645  page =  [ "HTML", "AMP", "JSON" ]
   646  
   647  `
   648  
   649  	pageTemplate := `---
   650  title: "%s"
   651  ---
   652  # Doc
   653  
   654  {{< myShort >}}
   655  {{< noExt >}}
   656  {{%% onlyHTML %%}}
   657  
   658  {{< myInner >}}{{< myShort >}}{{< /myInner >}}
   659  
   660  `
   661  
   662  	pageTemplateCSVOnly := `---
   663  title: "%s"
   664  outputs: ["CSV"]
   665  ---
   666  # Doc
   667  
   668  CSV: {{< myShort >}}
   669  `
   670  
   671  	pageTemplateShortcodeNotFound := `---
   672  title: "%s"
   673  outputs: ["CSV"]
   674  ---
   675  # Doc
   676  
   677  NotFound: {{< thisDoesNotExist >}}
   678  `
   679  
   680  	mf := afero.NewMemMapFs()
   681  
   682  	th, h := newTestSitesFromConfig(t, mf, siteConfig,
   683  		"layouts/_default/single.html", `Single HTML: {{ .Title }}|{{ .Content }}`,
   684  		"layouts/_default/single.json", `Single JSON: {{ .Title }}|{{ .Content }}`,
   685  		"layouts/_default/single.csv", `Single CSV: {{ .Title }}|{{ .Content }}`,
   686  		"layouts/index.html", `Home HTML: {{ .Title }}|{{ .Content }}`,
   687  		"layouts/index.amp.html", `Home AMP: {{ .Title }}|{{ .Content }}`,
   688  		"layouts/index.ics", `Home Calendar: {{ .Title }}|{{ .Content }}`,
   689  		"layouts/shortcodes/myShort.html", `ShortHTML`,
   690  		"layouts/shortcodes/myShort.amp.html", `ShortAMP`,
   691  		"layouts/shortcodes/myShort.csv", `ShortCSV`,
   692  		"layouts/shortcodes/myShort.ics", `ShortCalendar`,
   693  		"layouts/shortcodes/myShort.json", `ShortJSON`,
   694  		"layouts/shortcodes/noExt", `ShortNoExt`,
   695  		"layouts/shortcodes/onlyHTML.html", `ShortOnlyHTML`,
   696  		"layouts/shortcodes/myInner.html", `myInner:--{{- .Inner -}}--`,
   697  	)
   698  
   699  	fs := th.Fs
   700  
   701  	writeSource(t, fs, "content/_index.md", fmt.Sprintf(pageTemplate, "Home"))
   702  	writeSource(t, fs, "content/sect/mypage.md", fmt.Sprintf(pageTemplate, "Single"))
   703  	writeSource(t, fs, "content/sect/mycsvpage.md", fmt.Sprintf(pageTemplateCSVOnly, "Single CSV"))
   704  	writeSource(t, fs, "content/sect/notfound.md", fmt.Sprintf(pageTemplateShortcodeNotFound, "Single CSV"))
   705  
   706  	err := h.Build(BuildCfg{})
   707  	require.Equal(t, "logged 1 error(s)", err.Error())
   708  	require.Len(t, h.Sites, 1)
   709  
   710  	s := h.Sites[0]
   711  	home := s.getPage(KindHome)
   712  	require.NotNil(t, home)
   713  	require.Len(t, home.outputFormats, 3)
   714  
   715  	th.assertFileContent("public/index.html",
   716  		"Home HTML",
   717  		"ShortHTML",
   718  		"ShortNoExt",
   719  		"ShortOnlyHTML",
   720  		"myInner:--ShortHTML--",
   721  	)
   722  
   723  	th.assertFileContent("public/amp/index.html",
   724  		"Home AMP",
   725  		"ShortAMP",
   726  		"ShortNoExt",
   727  		"ShortOnlyHTML",
   728  		"myInner:--ShortAMP--",
   729  	)
   730  
   731  	th.assertFileContent("public/index.ics",
   732  		"Home Calendar",
   733  		"ShortCalendar",
   734  		"ShortNoExt",
   735  		"ShortOnlyHTML",
   736  		"myInner:--ShortCalendar--",
   737  	)
   738  
   739  	th.assertFileContent("public/sect/mypage/index.html",
   740  		"Single HTML",
   741  		"ShortHTML",
   742  		"ShortNoExt",
   743  		"ShortOnlyHTML",
   744  		"myInner:--ShortHTML--",
   745  	)
   746  
   747  	th.assertFileContent("public/sect/mypage/index.json",
   748  		"Single JSON",
   749  		"ShortJSON",
   750  		"ShortNoExt",
   751  		"ShortOnlyHTML",
   752  		"myInner:--ShortJSON--",
   753  	)
   754  
   755  	th.assertFileContent("public/amp/sect/mypage/index.html",
   756  		// No special AMP template
   757  		"Single HTML",
   758  		"ShortAMP",
   759  		"ShortNoExt",
   760  		"ShortOnlyHTML",
   761  		"myInner:--ShortAMP--",
   762  	)
   763  
   764  	th.assertFileContent("public/sect/mycsvpage/index.csv",
   765  		"Single CSV",
   766  		"ShortCSV",
   767  	)
   768  
   769  	th.assertFileContent("public/sect/notfound/index.csv",
   770  		"NotFound:",
   771  		"thisDoesNotExist",
   772  	)
   773  
   774  	require.Equal(t, uint64(1), s.Log.LogCountForLevel(jww.LevelError))
   775  
   776  }
   777  
   778  func collectAndSortShortcodes(shortcodes *orderedMap) []string {
   779  	var asArray []string
   780  
   781  	for _, key := range shortcodes.Keys() {
   782  		sc := shortcodes.getShortcode(key)
   783  		asArray = append(asArray, fmt.Sprintf("%s:%s", key, sc))
   784  	}
   785  
   786  	sort.Strings(asArray)
   787  	return asArray
   788  
   789  }
   790  
   791  func BenchmarkReplaceShortcodeTokens(b *testing.B) {
   792  
   793  	type input struct {
   794  		in           []byte
   795  		replacements map[string]string
   796  		expect       []byte
   797  	}
   798  
   799  	data := []struct {
   800  		input        string
   801  		replacements map[string]string
   802  		expect       []byte
   803  	}{
   804  		{"Hello HAHAHUGOSHORTCODE-1HBHB.", map[string]string{"HAHAHUGOSHORTCODE-1HBHB": "World"}, []byte("Hello World.")},
   805  		{strings.Repeat("A", 100) + " HAHAHUGOSHORTCODE-1HBHB.", map[string]string{"HAHAHUGOSHORTCODE-1HBHB": "Hello World"}, []byte(strings.Repeat("A", 100) + " Hello World.")},
   806  		{strings.Repeat("A", 500) + " HAHAHUGOSHORTCODE-1HBHB.", map[string]string{"HAHAHUGOSHORTCODE-1HBHB": "Hello World"}, []byte(strings.Repeat("A", 500) + " Hello World.")},
   807  		{strings.Repeat("ABCD ", 500) + " HAHAHUGOSHORTCODE-1HBHB.", map[string]string{"HAHAHUGOSHORTCODE-1HBHB": "Hello World"}, []byte(strings.Repeat("ABCD ", 500) + " Hello World.")},
   808  		{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.")},
   809  	}
   810  
   811  	var in = make([]input, b.N*len(data))
   812  	var cnt = 0
   813  	for i := 0; i < b.N; i++ {
   814  		for _, this := range data {
   815  			in[cnt] = input{[]byte(this.input), this.replacements, this.expect}
   816  			cnt++
   817  		}
   818  	}
   819  
   820  	b.ResetTimer()
   821  	cnt = 0
   822  	for i := 0; i < b.N; i++ {
   823  		for j := range data {
   824  			currIn := in[cnt]
   825  			cnt++
   826  			results, err := replaceShortcodeTokens(currIn.in, "HUGOSHORTCODE", currIn.replacements)
   827  
   828  			if err != nil {
   829  				b.Fatalf("[%d] failed: %s", i, err)
   830  				continue
   831  			}
   832  			if len(results) != len(currIn.expect) {
   833  				b.Fatalf("[%d] replaceShortcodeTokens, got \n%q but expected \n%q", j, results, currIn.expect)
   834  			}
   835  
   836  		}
   837  
   838  	}
   839  }
   840  
   841  func TestReplaceShortcodeTokens(t *testing.T) {
   842  	t.Parallel()
   843  	for i, this := range []struct {
   844  		input        string
   845  		prefix       string
   846  		replacements map[string]string
   847  		expect       interface{}
   848  	}{
   849  		{"Hello HAHAPREFIX-1HBHB.", "PREFIX", map[string]string{"HAHAPREFIX-1HBHB": "World"}, "Hello World."},
   850  		{"Hello HAHAPREFIX-1@}@.", "PREFIX", map[string]string{"HAHAPREFIX-1HBHB": "World"}, false},
   851  		{"HAHAPREFIX2-1HBHB", "PREFIX2", map[string]string{"HAHAPREFIX2-1HBHB": "World"}, "World"},
   852  		{"Hello World!", "PREFIX2", map[string]string{}, "Hello World!"},
   853  		{"!HAHAPREFIX-1HBHB", "PREFIX", map[string]string{"HAHAPREFIX-1HBHB": "World"}, "!World"},
   854  		{"HAHAPREFIX-1HBHB!", "PREFIX", map[string]string{"HAHAPREFIX-1HBHB": "World"}, "World!"},
   855  		{"!HAHAPREFIX-1HBHB!", "PREFIX", map[string]string{"HAHAPREFIX-1HBHB": "World"}, "!World!"},
   856  		{"_{_PREFIX-1HBHB", "PREFIX", map[string]string{"HAHAPREFIX-1HBHB": "World"}, "_{_PREFIX-1HBHB"},
   857  		{"Hello HAHAPREFIX-1HBHB.", "PREFIX", map[string]string{"HAHAPREFIX-1HBHB": "To You My Old Friend Who Told Me This Fantastic Story"}, "Hello To You My Old Friend Who Told Me This Fantastic Story."},
   858  		{"A HAHAA-1HBHB asdf HAHAA-2HBHB.", "A", map[string]string{"HAHAA-1HBHB": "v1", "HAHAA-2HBHB": "v2"}, "A v1 asdf v2."},
   859  		{"Hello HAHAPREFIX2-1HBHB. Go HAHAPREFIX2-2HBHB, Go, Go HAHAPREFIX2-3HBHB Go Go!.", "PREFIX2", map[string]string{"HAHAPREFIX2-1HBHB": "Europe", "HAHAPREFIX2-2HBHB": "Jonny", "HAHAPREFIX2-3HBHB": "Johnny"}, "Hello Europe. Go Jonny, Go, Go Johnny Go Go!."},
   860  		{"A HAHAPREFIX-2HBHB HAHAPREFIX-1HBHB.", "PREFIX", map[string]string{"HAHAPREFIX-1HBHB": "A", "HAHAPREFIX-2HBHB": "B"}, "A B A."},
   861  		{"A HAHAPREFIX-1HBHB HAHAPREFIX-2", "PREFIX", map[string]string{"HAHAPREFIX-1HBHB": "A"}, false},
   862  		{"A HAHAPREFIX-1HBHB but not the second.", "PREFIX", map[string]string{"HAHAPREFIX-1HBHB": "A", "HAHAPREFIX-2HBHB": "B"}, "A A but not the second."},
   863  		{"An HAHAPREFIX-1HBHB.", "PREFIX", map[string]string{"HAHAPREFIX-1HBHB": "A", "HAHAPREFIX-2HBHB": "B"}, "An A."},
   864  		{"An HAHAPREFIX-1HBHB HAHAPREFIX-2HBHB.", "PREFIX", map[string]string{"HAHAPREFIX-1HBHB": "A", "HAHAPREFIX-2HBHB": "B"}, "An A B."},
   865  		{"A HAHAPREFIX-1HBHB HAHAPREFIX-2HBHB HAHAPREFIX-3HBHB HAHAPREFIX-1HBHB HAHAPREFIX-3HBHB.", "PREFIX", map[string]string{"HAHAPREFIX-1HBHB": "A", "HAHAPREFIX-2HBHB": "B", "HAHAPREFIX-3HBHB": "C"}, "A A B C A C."},
   866  		{"A HAHAPREFIX-1HBHB HAHAPREFIX-2HBHB HAHAPREFIX-3HBHB HAHAPREFIX-1HBHB HAHAPREFIX-3HBHB.", "PREFIX", map[string]string{"HAHAPREFIX-1HBHB": "A", "HAHAPREFIX-2HBHB": "B", "HAHAPREFIX-3HBHB": "C"}, "A A B C A C."},
   867  		// Issue #1148 remove p-tags 10 =>
   868  		{"Hello <p>HAHAPREFIX-1HBHB</p>. END.", "PREFIX", map[string]string{"HAHAPREFIX-1HBHB": "World"}, "Hello World. END."},
   869  		{"Hello <p>HAHAPREFIX-1HBHB</p>. <p>HAHAPREFIX-2HBHB</p> END.", "PREFIX", map[string]string{"HAHAPREFIX-1HBHB": "World", "HAHAPREFIX-2HBHB": "THE"}, "Hello World. THE END."},
   870  		{"Hello <p>HAHAPREFIX-1HBHB. END</p>.", "PREFIX", map[string]string{"HAHAPREFIX-1HBHB": "World"}, "Hello <p>World. END</p>."},
   871  		{"<p>Hello HAHAPREFIX-1HBHB</p>. END.", "PREFIX", map[string]string{"HAHAPREFIX-1HBHB": "World"}, "<p>Hello World</p>. END."},
   872  		{"Hello <p>HAHAPREFIX-1HBHB12", "PREFIX", map[string]string{"HAHAPREFIX-1HBHB": "World"}, "Hello <p>World12"},
   873  		{"Hello HAHAP-1HBHB. HAHAP-1HBHB-HAHAP-1HBHB HAHAP-1HBHB HAHAP-1HBHB HAHAP-1HBHB END", "P", map[string]string{"HAHAP-1HBHB": strings.Repeat("BC", 100)},
   874  			fmt.Sprintf("Hello %s. %s-%s %s %s %s END",
   875  				strings.Repeat("BC", 100), strings.Repeat("BC", 100), strings.Repeat("BC", 100), strings.Repeat("BC", 100), strings.Repeat("BC", 100), strings.Repeat("BC", 100))},
   876  	} {
   877  
   878  		results, err := replaceShortcodeTokens([]byte(this.input), this.prefix, this.replacements)
   879  
   880  		if b, ok := this.expect.(bool); ok && !b {
   881  			if err == nil {
   882  				t.Errorf("[%d] replaceShortcodeTokens didn't return an expected error", i)
   883  			}
   884  		} else {
   885  			if err != nil {
   886  				t.Errorf("[%d] failed: %s", i, err)
   887  				continue
   888  			}
   889  			if !reflect.DeepEqual(results, []byte(this.expect.(string))) {
   890  				t.Errorf("[%d] replaceShortcodeTokens, got \n%q but expected \n%q", i, results, this.expect)
   891  			}
   892  		}
   893  
   894  	}
   895  
   896  }
   897  
   898  func TestScKey(t *testing.T) {
   899  	require.Equal(t, scKey{Suffix: "xml", ShortcodePlaceholder: "ABCD"},
   900  		newScKey(media.XMLType, "ABCD"))
   901  	require.Equal(t, scKey{Lang: "en", Suffix: "html", OutputFormat: "AMP", ShortcodePlaceholder: "EFGH"},
   902  		newScKeyFromLangAndOutputFormat("en", output.AMPFormat, "EFGH"))
   903  	require.Equal(t, scKey{Suffix: "html", ShortcodePlaceholder: "IJKL"},
   904  		newDefaultScKey("IJKL"))
   905  
   906  }
   907  
   908  func TestShortcodeGetContent(t *testing.T) {
   909  	t.Parallel()
   910  	assert := require.New(t)
   911  
   912  	contentShortcode := `
   913  {{- $t := .Get 0 -}}
   914  {{- $p := .Get 1 -}}
   915  {{- $k := .Get 2 -}}
   916  {{- $page := $.Page.Site.GetPage "page" $p -}}
   917  {{ if $page }}
   918  {{- if eq $t "bundle" -}}
   919  {{- .Scratch.Set "p" ($page.Resources.GetMatch (printf "%s*" $k)) -}}
   920  {{- else -}}
   921  {{- $.Scratch.Set "p" $page -}}
   922  {{- end -}}P1:{{ .Page.Content }}|P2:{{ $p := ($.Scratch.Get "p") }}{{ $p.Title }}/{{ $p.Content }}|
   923  {{- else -}}
   924  {{- errorf "Page %s is nil" $p -}}
   925  {{- end -}}
   926  `
   927  
   928  	var templates []string
   929  	var content []string
   930  
   931  	contentWithShortcodeTemplate := `---
   932  title: doc%s
   933  weight: %d
   934  ---
   935  Logo:{{< c "bundle" "b1" "logo.png" >}}:P1: {{< c "page" "section1/p1" "" >}}:BP1:{{< c "bundle" "b1" "bp1" >}}`
   936  
   937  	simpleContentTemplate := `---
   938  title: doc%s
   939  weight: %d
   940  ---
   941  C-%s`
   942  
   943  	v := viper.New()
   944  
   945  	v.Set("timeout", 500)
   946  
   947  	templates = append(templates, []string{"shortcodes/c.html", contentShortcode}...)
   948  	templates = append(templates, []string{"_default/single.html", "Single Content: {{ .Content }}"}...)
   949  	templates = append(templates, []string{"_default/list.html", "List Content: {{ .Content }}"}...)
   950  
   951  	content = append(content, []string{"b1/index.md", fmt.Sprintf(contentWithShortcodeTemplate, "b1", 1)}...)
   952  	content = append(content, []string{"b1/logo.png", "PNG logo"}...)
   953  	content = append(content, []string{"b1/bp1.md", fmt.Sprintf(simpleContentTemplate, "bp1", 1, "bp1")}...)
   954  
   955  	content = append(content, []string{"section1/_index.md", fmt.Sprintf(contentWithShortcodeTemplate, "s1", 2)}...)
   956  	content = append(content, []string{"section1/p1.md", fmt.Sprintf(simpleContentTemplate, "s1p1", 2, "s1p1")}...)
   957  
   958  	content = append(content, []string{"section2/_index.md", fmt.Sprintf(simpleContentTemplate, "b1", 1, "b1")}...)
   959  	content = append(content, []string{"section2/s2p1.md", fmt.Sprintf(contentWithShortcodeTemplate, "bp1", 1)}...)
   960  
   961  	builder := newTestSitesBuilder(t).WithDefaultMultiSiteConfig()
   962  
   963  	builder.WithViper(v).WithContent(content...).WithTemplates(templates...).CreateSites().Build(BuildCfg{})
   964  	s := builder.H.Sites[0]
   965  	assert.Equal(3, len(s.RegularPages))
   966  
   967  	builder.AssertFileContent("public/section1/index.html",
   968  		"List Content: <p>Logo:P1:|P2:logo.png/PNG logo|:P1: P1:|P2:docs1p1/<p>C-s1p1</p>\n|",
   969  		"BP1:P1:|P2:docbp1/<p>C-bp1</p>",
   970  	)
   971  
   972  	builder.AssertFileContent("public/b1/index.html",
   973  		"Single Content: <p>Logo:P1:|P2:logo.png/PNG logo|:P1: P1:|P2:docs1p1/<p>C-s1p1</p>\n|",
   974  		"P2:docbp1/<p>C-bp1</p>",
   975  	)
   976  
   977  	builder.AssertFileContent("public/section2/s2p1/index.html",
   978  		"Single Content: <p>Logo:P1:|P2:logo.png/PNG logo|:P1: P1:|P2:docs1p1/<p>C-s1p1</p>\n|",
   979  		"P2:docbp1/<p>C-bp1</p>",
   980  	)
   981  
   982  }
   983  
   984  func TestShortcodePreserveOrder(t *testing.T) {
   985  	t.Parallel()
   986  	assert := require.New(t)
   987  
   988  	contentTemplate := `---
   989  title: doc%d
   990  weight: %d
   991  ---
   992  # doc
   993  
   994  {{< s1 >}}{{< s2 >}}{{< s3 >}}{{< s4 >}}{{< s5 >}}
   995  
   996  {{< nested >}}
   997  {{< ordinal >}} {{< scratch >}}
   998  {{< ordinal >}} {{< scratch >}}
   999  {{< ordinal >}} {{< scratch >}}
  1000  {{< /nested >}}
  1001  
  1002  `
  1003  
  1004  	ordinalShortcodeTemplate := `ordinal: {{ .Ordinal }}{{ .Page.Scratch.Set "ordinal" .Ordinal }}`
  1005  
  1006  	nestedShortcode := `outer ordinal: {{ .Ordinal }} inner: {{ .Inner }}`
  1007  	scratchGetShortcode := `scratch ordinal: {{ .Ordinal }} scratch get ordinal: {{ .Page.Scratch.Get "ordinal" }}`
  1008  	shortcodeTemplate := `v%d: {{ .Ordinal }} sgo: {{ .Page.Scratch.Get "o2" }}{{ .Page.Scratch.Set "o2" .Ordinal }}|`
  1009  
  1010  	var shortcodes []string
  1011  	var content []string
  1012  
  1013  	shortcodes = append(shortcodes, []string{"shortcodes/nested.html", nestedShortcode}...)
  1014  	shortcodes = append(shortcodes, []string{"shortcodes/ordinal.html", ordinalShortcodeTemplate}...)
  1015  	shortcodes = append(shortcodes, []string{"shortcodes/scratch.html", scratchGetShortcode}...)
  1016  
  1017  	for i := 1; i <= 5; i++ {
  1018  		sc := fmt.Sprintf(shortcodeTemplate, i)
  1019  		sc = strings.Replace(sc, "%%", "%", -1)
  1020  		shortcodes = append(shortcodes, []string{fmt.Sprintf("shortcodes/s%d.html", i), sc}...)
  1021  	}
  1022  
  1023  	for i := 1; i <= 3; i++ {
  1024  		content = append(content, []string{fmt.Sprintf("p%d.md", i), fmt.Sprintf(contentTemplate, i, i)}...)
  1025  	}
  1026  
  1027  	builder := newTestSitesBuilder(t).WithDefaultMultiSiteConfig()
  1028  
  1029  	builder.WithContent(content...).WithTemplatesAdded(shortcodes...).CreateSites().Build(BuildCfg{})
  1030  
  1031  	s := builder.H.Sites[0]
  1032  	assert.Equal(3, len(s.RegularPages))
  1033  
  1034  	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`)
  1035  	builder.AssertFileContent("public/en/p1/index.html", `outer ordinal: 5 inner: 
  1036  ordinal: 0 scratch ordinal: 1 scratch get ordinal: 0
  1037  ordinal: 2 scratch ordinal: 3 scratch get ordinal: 2
  1038  ordinal: 4 scratch ordinal: 5 scratch get ordinal: 4`)
  1039  
  1040  }