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 }