github.com/fighterlyt/hugo@v0.47.1/tpl/tplimpl/template_ast_transformers_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  package tplimpl
    14  
    15  import (
    16  	"bytes"
    17  	"fmt"
    18  	"testing"
    19  
    20  	"html/template"
    21  
    22  	"github.com/stretchr/testify/require"
    23  )
    24  
    25  var (
    26  	testFuncs = map[string]interface{}{
    27  		"First": func(v ...interface{}) interface{} { return v[0] },
    28  		"Echo":  func(v interface{}) interface{} { return v },
    29  		"where": func(seq, key interface{}, args ...interface{}) (interface{}, error) {
    30  			return map[string]interface{}{
    31  				"ByWeight": fmt.Sprintf("%v:%v:%v", seq, key, args),
    32  			}, nil
    33  		},
    34  	}
    35  
    36  	paramsData = map[string]interface{}{
    37  		"NotParam": "Hi There",
    38  		"Slice":    []int{1, 3},
    39  		"Params": map[string]interface{}{
    40  			"lower": "P1L",
    41  			"slice": []int{1, 3},
    42  		},
    43  		"Pages": map[string]interface{}{
    44  			"ByWeight": []int{1, 3},
    45  		},
    46  		"CurrentSection": map[string]interface{}{
    47  			"Params": map[string]interface{}{
    48  				"lower": "pcurrentsection",
    49  			},
    50  		},
    51  		"Site": map[string]interface{}{
    52  			"Params": map[string]interface{}{
    53  				"lower": "P2L",
    54  				"slice": []int{1, 3},
    55  			},
    56  			"Language": map[string]interface{}{
    57  				"Params": map[string]interface{}{
    58  					"lower": "P22L",
    59  					"nested": map[string]interface{}{
    60  						"lower": "P22L_nested",
    61  					},
    62  				},
    63  			},
    64  			"Data": map[string]interface{}{
    65  				"Params": map[string]interface{}{
    66  					"NOLOW": "P3H",
    67  				},
    68  			},
    69  		},
    70  	}
    71  
    72  	paramsTempl = `
    73  {{ $page := . }}
    74  {{ $pages := .Pages }}
    75  {{ $pageParams := .Params }}
    76  {{ $site := .Site }}
    77  {{ $siteParams := .Site.Params }}
    78  {{ $data := .Site.Data }}
    79  {{ $notparam := .NotParam }}
    80  
    81  PCurrentSection: {{ .CurrentSection.Params.LOWER }}
    82  P1: {{ .Params.LOWER }}
    83  P1_2: {{ $.Params.LOWER }}
    84  P1_3: {{ $page.Params.LOWER }}
    85  P1_4: {{ $pageParams.LOWER }}
    86  P2: {{ .Site.Params.LOWER }}
    87  P2_2: {{ $.Site.Params.LOWER }}
    88  P2_3: {{ $site.Params.LOWER }}
    89  P2_4: {{ $siteParams.LOWER }}
    90  P22: {{ .Site.Language.Params.LOWER }}
    91  P22_nested: {{ .Site.Language.Params.NESTED.LOWER }}
    92  P3: {{ .Site.Data.Params.NOLOW }}
    93  P3_2: {{ $.Site.Data.Params.NOLOW }}
    94  P3_3: {{ $site.Data.Params.NOLOW }}
    95  P3_4: {{ $data.Params.NOLOW }}
    96  P4: {{ range $i, $e := .Site.Params.SLICE }}{{ $e }}{{ end }}
    97  P5: {{ Echo .Params.LOWER }}
    98  P5_2: {{ Echo $site.Params.LOWER }}
    99  {{ if .Params.LOWER }}
   100  IF: {{ .Params.LOWER }}
   101  {{ end }}
   102  {{ if .Params.NOT_EXIST }}
   103  {{ else }}
   104  ELSE: {{ .Params.LOWER }}
   105  {{ end }}
   106  
   107  
   108  {{ with .Params.LOWER }}
   109  WITH: {{ . }}
   110  {{ end }}
   111  
   112  
   113  {{ range .Slice }}
   114  RANGE: {{ . }}: {{ $.Params.LOWER }}
   115  {{ end }}
   116  {{ index .Slice 1 }}
   117  {{ .NotParam }}
   118  {{ .NotParam }}
   119  {{ .NotParam }}
   120  {{ .NotParam }}
   121  {{ .NotParam }}
   122  {{ .NotParam }}
   123  {{ .NotParam }}
   124  {{ .NotParam }}
   125  {{ .NotParam }}
   126  {{ .NotParam }}
   127  {{ $notparam }}
   128  
   129  
   130  {{ $lower := .Site.Params.LOWER }}
   131  F1: {{ printf "themes/%s-theme" .Site.Params.LOWER }}
   132  F2: {{ Echo (printf "themes/%s-theme" $lower) }}
   133  F3: {{ Echo (printf "themes/%s-theme" .Site.Params.LOWER) }}
   134  
   135  PSLICE: {{ range .Params.SLICE }}PSLICE{{.}}|{{ end }}
   136  
   137  {{ $pages := "foo" }}
   138  {{ $pages := where $pages ".Params.toc_hide" "!=" true }}
   139  PARAMS STRING: {{ $pages.ByWeight }}
   140  PARAMS STRING2: {{ with $pages }}{{ .ByWeight }}{{ end }}
   141  {{ $pages3 := where ".Params.TOC_HIDE" "!=" .Params.LOWER }}
   142  PARAMS STRING3: {{ $pages3.ByWeight }}
   143  {{ $first := First .Pages .Site.Params.LOWER }}
   144  PARAMS COMPOSITE: {{ $first.ByWeight }}
   145  `
   146  )
   147  
   148  func TestParamsKeysToLower(t *testing.T) {
   149  	t.Parallel()
   150  
   151  	require.Error(t, applyTemplateTransformers(nil, nil))
   152  
   153  	templ, err := template.New("foo").Funcs(testFuncs).Parse(paramsTempl)
   154  
   155  	require.NoError(t, err)
   156  
   157  	c := newTemplateContext(createParseTreeLookup(templ))
   158  
   159  	require.Equal(t, -1, c.decl.indexOfReplacementStart([]string{}))
   160  
   161  	c.paramsKeysToLower(templ.Tree.Root)
   162  
   163  	var b bytes.Buffer
   164  
   165  	require.NoError(t, templ.Execute(&b, paramsData))
   166  
   167  	result := b.String()
   168  
   169  	require.Contains(t, result, "P1: P1L")
   170  	require.Contains(t, result, "P1_2: P1L")
   171  	require.Contains(t, result, "P1_3: P1L")
   172  	require.Contains(t, result, "P1_4: P1L")
   173  	require.Contains(t, result, "P2: P2L")
   174  	require.Contains(t, result, "P2_2: P2L")
   175  	require.Contains(t, result, "P2_3: P2L")
   176  	require.Contains(t, result, "P2_4: P2L")
   177  	require.Contains(t, result, "P22: P22L")
   178  	require.Contains(t, result, "P22_nested: P22L_nested")
   179  	require.Contains(t, result, "P3: P3H")
   180  	require.Contains(t, result, "P3_2: P3H")
   181  	require.Contains(t, result, "P3_3: P3H")
   182  	require.Contains(t, result, "P3_4: P3H")
   183  	require.Contains(t, result, "P4: 13")
   184  	require.Contains(t, result, "P5: P1L")
   185  	require.Contains(t, result, "P5_2: P2L")
   186  
   187  	require.Contains(t, result, "IF: P1L")
   188  	require.Contains(t, result, "ELSE: P1L")
   189  
   190  	require.Contains(t, result, "WITH: P1L")
   191  
   192  	require.Contains(t, result, "RANGE: 3: P1L")
   193  
   194  	require.Contains(t, result, "Hi There")
   195  
   196  	// Issue #2740
   197  	require.Contains(t, result, "F1: themes/P2L-theme")
   198  	require.Contains(t, result, "F2: themes/P2L-theme")
   199  	require.Contains(t, result, "F3: themes/P2L-theme")
   200  
   201  	require.Contains(t, result, "PSLICE: PSLICE1|PSLICE3|")
   202  	require.Contains(t, result, "PARAMS STRING: foo:.Params.toc_hide:[!= true]")
   203  	require.Contains(t, result, "PARAMS STRING2: foo:.Params.toc_hide:[!= true]")
   204  	require.Contains(t, result, "PARAMS STRING3: .Params.TOC_HIDE:!=:[P1L]")
   205  
   206  	// Issue #5094
   207  	require.Contains(t, result, "PARAMS COMPOSITE: [1 3]")
   208  
   209  	// Issue #5068
   210  	require.Contains(t, result, "PCurrentSection: pcurrentsection")
   211  
   212  }
   213  
   214  func BenchmarkTemplateParamsKeysToLower(b *testing.B) {
   215  	templ, err := template.New("foo").Funcs(testFuncs).Parse(paramsTempl)
   216  
   217  	if err != nil {
   218  		b.Fatal(err)
   219  	}
   220  
   221  	templates := make([]*template.Template, b.N)
   222  
   223  	for i := 0; i < b.N; i++ {
   224  		templates[i], err = templ.Clone()
   225  		if err != nil {
   226  			b.Fatal(err)
   227  		}
   228  	}
   229  
   230  	b.ResetTimer()
   231  
   232  	for i := 0; i < b.N; i++ {
   233  		c := newTemplateContext(createParseTreeLookup(templates[i]))
   234  		c.paramsKeysToLower(templ.Tree.Root)
   235  	}
   236  }
   237  
   238  func TestParamsKeysToLowerVars(t *testing.T) {
   239  	t.Parallel()
   240  	var (
   241  		ctx = map[string]interface{}{
   242  			"Params": map[string]interface{}{
   243  				"colors": map[string]interface{}{
   244  					"blue": "Amber",
   245  					"pretty": map[string]interface{}{
   246  						"first": "Indigo",
   247  					},
   248  				},
   249  			},
   250  		}
   251  
   252  		// This is how Amber behaves:
   253  		paramsTempl = `
   254  {{$__amber_1 := .Params.Colors}}
   255  {{$__amber_2 := $__amber_1.Blue}}
   256  {{$__amber_3 := $__amber_1.Pretty}}
   257  {{$__amber_4 := .Params}}
   258  
   259  Color: {{$__amber_2}}
   260  Blue: {{ $__amber_1.Blue}}
   261  Pretty First1: {{ $__amber_3.First}}
   262  Pretty First2: {{ $__amber_1.Pretty.First}}
   263  Pretty First3: {{ $__amber_4.COLORS.PRETTY.FIRST}}
   264  `
   265  	)
   266  
   267  	templ, err := template.New("foo").Parse(paramsTempl)
   268  
   269  	require.NoError(t, err)
   270  
   271  	c := newTemplateContext(createParseTreeLookup(templ))
   272  
   273  	c.paramsKeysToLower(templ.Tree.Root)
   274  
   275  	var b bytes.Buffer
   276  
   277  	require.NoError(t, templ.Execute(&b, ctx))
   278  
   279  	result := b.String()
   280  
   281  	require.Contains(t, result, "Color: Amber")
   282  	require.Contains(t, result, "Blue: Amber")
   283  	require.Contains(t, result, "Pretty First1: Indigo")
   284  	require.Contains(t, result, "Pretty First2: Indigo")
   285  	require.Contains(t, result, "Pretty First3: Indigo")
   286  
   287  }
   288  
   289  func TestParamsKeysToLowerInBlockTemplate(t *testing.T) {
   290  	t.Parallel()
   291  
   292  	var (
   293  		ctx = map[string]interface{}{
   294  			"Params": map[string]interface{}{
   295  				"lower": "P1L",
   296  			},
   297  		}
   298  
   299  		master = `
   300  P1: {{ .Params.LOWER }}
   301  {{ block "main" . }}DEFAULT{{ end }}`
   302  		overlay = `
   303  {{ define "main" }}
   304  P2: {{ .Params.LOWER }}
   305  {{ end }}`
   306  	)
   307  
   308  	masterTpl, err := template.New("foo").Parse(master)
   309  	require.NoError(t, err)
   310  
   311  	overlayTpl, err := template.Must(masterTpl.Clone()).Parse(overlay)
   312  	require.NoError(t, err)
   313  	overlayTpl = overlayTpl.Lookup(overlayTpl.Name())
   314  
   315  	c := newTemplateContext(createParseTreeLookup(overlayTpl))
   316  
   317  	c.paramsKeysToLower(overlayTpl.Tree.Root)
   318  
   319  	var b bytes.Buffer
   320  
   321  	require.NoError(t, overlayTpl.Execute(&b, ctx))
   322  
   323  	result := b.String()
   324  
   325  	require.Contains(t, result, "P1: P1L")
   326  	require.Contains(t, result, "P2: P1L")
   327  }
   328  
   329  // Issue #2927
   330  func TestTransformRecursiveTemplate(t *testing.T) {
   331  
   332  	recursive := `
   333  {{ define "menu-nodes" }}
   334  {{ template "menu-node" }}
   335  {{ end }}
   336  {{ define "menu-node" }}
   337  {{ template "menu-node" }}
   338  {{ end }}
   339  {{ template "menu-nodes" }}
   340  `
   341  
   342  	templ, err := template.New("foo").Parse(recursive)
   343  	require.NoError(t, err)
   344  
   345  	c := newTemplateContext(createParseTreeLookup(templ))
   346  	c.paramsKeysToLower(templ.Tree.Root)
   347  
   348  }