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 &#34;world&#34;. (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 &quot;foo&quot;;\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  }