github.com/kovansky/hugo@v0.92.3-0.20220224232819-63076e4ff19f/hugolib/resource_chain_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 "fmt" 18 "io" 19 "io/ioutil" 20 "math/rand" 21 "net/http" 22 "net/http/httptest" 23 "os" 24 "path/filepath" 25 "strings" 26 "testing" 27 "time" 28 29 "github.com/gohugoio/hugo/helpers" 30 31 qt "github.com/frankban/quicktest" 32 33 "github.com/gohugoio/hugo/common/loggers" 34 "github.com/gohugoio/hugo/resources/resource_transformers/tocss/scss" 35 ) 36 37 func TestResourceChainBasic(t *testing.T) { 38 ts := httptest.NewServer(http.FileServer(http.Dir("testdata/"))) 39 t.Cleanup(func() { 40 ts.Close() 41 }) 42 43 b := newTestSitesBuilder(t) 44 b.WithTemplatesAdded("index.html", fmt.Sprintf(` 45 {{ $hello := "<h1> Hello World! </h1>" | resources.FromString "hello.html" | fingerprint "sha512" | minify | fingerprint }} 46 {{ $cssFingerprinted1 := "body { background-color: lightblue; }" | resources.FromString "styles.css" | minify | fingerprint }} 47 {{ $cssFingerprinted2 := "body { background-color: orange; }" | resources.FromString "styles2.css" | minify | fingerprint }} 48 49 50 HELLO: {{ $hello.Name }}|{{ $hello.RelPermalink }}|{{ $hello.Content | safeHTML }} 51 52 {{ $img := resources.Get "images/sunset.jpg" }} 53 {{ $fit := $img.Fit "200x200" }} 54 {{ $fit2 := $fit.Fit "100x200" }} 55 {{ $img = $img | fingerprint }} 56 SUNSET: {{ $img.Name }}|{{ $img.RelPermalink }}|{{ $img.Width }}|{{ len $img.Content }} 57 FIT: {{ $fit.Name }}|{{ $fit.RelPermalink }}|{{ $fit.Width }} 58 CSS integrity Data first: {{ $cssFingerprinted1.Data.Integrity }} {{ $cssFingerprinted1.RelPermalink }} 59 CSS integrity Data last: {{ $cssFingerprinted2.RelPermalink }} {{ $cssFingerprinted2.Data.Integrity }} 60 61 {{ $rimg := resources.GetRemote "%[1]s/sunset.jpg" }} 62 {{ $remotenotfound := resources.GetRemote "%[1]s/notfound.jpg" }} 63 {{ $localnotfound := resources.Get "images/notfound.jpg" }} 64 {{ $gopherprotocol := resources.GetRemote "gopher://example.org" }} 65 {{ $rfit := $rimg.Fit "200x200" }} 66 {{ $rfit2 := $rfit.Fit "100x200" }} 67 {{ $rimg = $rimg | fingerprint }} 68 SUNSET REMOTE: {{ $rimg.Name }}|{{ $rimg.RelPermalink }}|{{ $rimg.Width }}|{{ len $rimg.Content }} 69 FIT REMOTE: {{ $rfit.Name }}|{{ $rfit.RelPermalink }}|{{ $rfit.Width }} 70 REMOTE NOT FOUND: {{ if $remotenotfound }}FAILED{{ else}}OK{{ end }} 71 LOCAL NOT FOUND: {{ if $localnotfound }}FAILED{{ else}}OK{{ end }} 72 PRINT PROTOCOL ERROR1: {{ with $gopherprotocol }}{{ . | safeHTML }}{{ end }} 73 PRINT PROTOCOL ERROR2: {{ with $gopherprotocol }}{{ .Err | safeHTML }}{{ end }} 74 75 `, ts.URL)) 76 77 fs := b.Fs.Source 78 79 imageDir := filepath.Join("assets", "images") 80 b.Assert(os.MkdirAll(imageDir, 0777), qt.IsNil) 81 src, err := os.Open("testdata/sunset.jpg") 82 b.Assert(err, qt.IsNil) 83 out, err := fs.Create(filepath.Join(imageDir, "sunset.jpg")) 84 b.Assert(err, qt.IsNil) 85 _, err = io.Copy(out, src) 86 b.Assert(err, qt.IsNil) 87 out.Close() 88 89 b.Running() 90 91 for i := 0; i < 2; i++ { 92 93 b.Build(BuildCfg{}) 94 95 b.AssertFileContent("public/index.html", 96 fmt.Sprintf(` 97 SUNSET: images/sunset.jpg|/images/sunset.a9bf1d944e19c0f382e0d8f51de690f7d0bc8fa97390c4242a86c3e5c0737e71.jpg|900|90587 98 FIT: images/sunset.jpg|/images/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_200x200_fit_q75_box.jpg|200 99 CSS integrity Data first: sha256-od9YaHw8nMOL8mUy97Sy8sKwMV3N4hI3aVmZXATxH+8= /styles.min.a1df58687c3c9cc38bf26532f7b4b2f2c2b0315dcde212376959995c04f11fef.css 100 CSS integrity Data last: /styles2.min.1cfc52986836405d37f9998a63fd6dd8608e8c410e5e3db1daaa30f78bc273ba.css sha256-HPxSmGg2QF03+ZmKY/1t2GCOjEEOXj2x2qow94vCc7o= 101 102 SUNSET REMOTE: sunset_%[1]s.jpg|/sunset_%[1]s.a9bf1d944e19c0f382e0d8f51de690f7d0bc8fa97390c4242a86c3e5c0737e71.jpg|900|90587 103 FIT REMOTE: sunset_%[1]s.jpg|/sunset_%[1]s_hu59e56ffff1bc1d8d122b1403d34e039f_0_200x200_fit_q75_box.jpg|200 104 REMOTE NOT FOUND: OK 105 LOCAL NOT FOUND: OK 106 PRINT PROTOCOL ERROR1: error calling resources.GetRemote: Get "gopher://example.org": unsupported protocol scheme "gopher" 107 PRINT PROTOCOL ERROR2: error calling resources.GetRemote: Get "gopher://example.org": unsupported protocol scheme "gopher" 108 109 110 `, helpers.HashString(ts.URL+"/sunset.jpg", map[string]interface{}{}))) 111 112 b.AssertFileContent("public/styles.min.a1df58687c3c9cc38bf26532f7b4b2f2c2b0315dcde212376959995c04f11fef.css", "body{background-color:#add8e6}") 113 b.AssertFileContent("public//styles2.min.1cfc52986836405d37f9998a63fd6dd8608e8c410e5e3db1daaa30f78bc273ba.css", "body{background-color:orange}") 114 115 b.EditFiles("page1.md", ` 116 --- 117 title: "Page 1 edit" 118 summary: "Edited summary" 119 --- 120 121 Edited content. 122 123 `) 124 125 b.Assert(b.Fs.Destination.Remove("public"), qt.IsNil) 126 b.H.ResourceSpec.ClearCaches() 127 128 } 129 } 130 131 func TestResourceChainPostProcess(t *testing.T) { 132 t.Parallel() 133 134 rnd := rand.New(rand.NewSource(time.Now().UnixNano())) 135 136 b := newTestSitesBuilder(t) 137 b.WithConfigFile("toml", `[minify] 138 minifyOutput = true 139 [minify.tdewolff] 140 [minify.tdewolff.html] 141 keepQuotes = false 142 keepWhitespace = false`) 143 b.WithContent("page1.md", "---\ntitle: Page1\n---") 144 b.WithContent("page2.md", "---\ntitle: Page2\n---") 145 146 b.WithTemplates( 147 "_default/single.html", `{{ $hello := "<h1> Hello World! </h1>" | resources.FromString "hello.html" | minify | fingerprint "md5" | resources.PostProcess }} 148 HELLO: {{ $hello.RelPermalink }} 149 `, 150 "index.html", `Start. 151 {{ $hello := "<h1> Hello World! </h1>" | resources.FromString "hello.html" | minify | fingerprint "md5" | resources.PostProcess }} 152 153 HELLO: {{ $hello.RelPermalink }}|Integrity: {{ $hello.Data.Integrity }}|MediaType: {{ $hello.MediaType.Type }} 154 HELLO2: Name: {{ $hello.Name }}|Content: {{ $hello.Content }}|Title: {{ $hello.Title }}|ResourceType: {{ $hello.ResourceType }} 155 156 // Issue #8884 157 <a href="hugo.rocks">foo</a> 158 <a href="{{ $hello.RelPermalink }}" integrity="{{ $hello.Data.Integrity}}">Hello</a> 159 `+strings.Repeat("a b", rnd.Intn(10)+1)+` 160 161 162 End.`) 163 164 b.Running() 165 b.Build(BuildCfg{}) 166 b.AssertFileContent("public/index.html", 167 `Start. 168 HELLO: /hello.min.a2d1cb24f24b322a7dad520414c523e9.html|Integrity: md5-otHLJPJLMip9rVIEFMUj6Q==|MediaType: text/html 169 HELLO2: Name: hello.html|Content: <h1>Hello World!</h1>|Title: hello.html|ResourceType: text 170 <a href=hugo.rocks>foo</a> 171 <a href="/hello.min.a2d1cb24f24b322a7dad520414c523e9.html" integrity="md5-otHLJPJLMip9rVIEFMUj6Q==">Hello</a> 172 End.`) 173 174 b.AssertFileContent("public/page1/index.html", `HELLO: /hello.min.a2d1cb24f24b322a7dad520414c523e9.html`) 175 b.AssertFileContent("public/page2/index.html", `HELLO: /hello.min.a2d1cb24f24b322a7dad520414c523e9.html`) 176 } 177 178 func BenchmarkResourceChainPostProcess(b *testing.B) { 179 for i := 0; i < b.N; i++ { 180 b.StopTimer() 181 s := newTestSitesBuilder(b) 182 for i := 0; i < 300; i++ { 183 s.WithContent(fmt.Sprintf("page%d.md", i+1), "---\ntitle: Page\n---") 184 } 185 s.WithTemplates("_default/single.html", `Start. 186 Some text. 187 188 189 {{ $hello1 := "<h1> Hello World 2! </h1>" | resources.FromString "hello.html" | minify | fingerprint "md5" | resources.PostProcess }} 190 {{ $hello2 := "<h1> Hello World 2! </h1>" | resources.FromString (printf "%s.html" .Path) | minify | fingerprint "md5" | resources.PostProcess }} 191 192 Some more text. 193 194 HELLO: {{ $hello1.RelPermalink }}|Integrity: {{ $hello1.Data.Integrity }}|MediaType: {{ $hello1.MediaType.Type }} 195 196 Some more text. 197 198 HELLO2: Name: {{ $hello2.Name }}|Content: {{ $hello2.Content }}|Title: {{ $hello2.Title }}|ResourceType: {{ $hello2.ResourceType }} 199 200 Some more text. 201 202 HELLO2_2: Name: {{ $hello2.Name }}|Content: {{ $hello2.Content }}|Title: {{ $hello2.Title }}|ResourceType: {{ $hello2.ResourceType }} 203 204 End. 205 `) 206 207 b.StartTimer() 208 s.Build(BuildCfg{}) 209 210 } 211 } 212 213 func TestResourceChains(t *testing.T) { 214 t.Parallel() 215 216 c := qt.New(t) 217 218 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 219 switch r.URL.Path { 220 case "/css/styles1.css": 221 w.Header().Set("Content-Type", "text/css") 222 w.Write([]byte(`h1 { 223 font-style: bold; 224 }`)) 225 return 226 227 case "/js/script1.js": 228 w.Write([]byte(`var x; x = 5, document.getElementById("demo").innerHTML = x * 10`)) 229 return 230 231 case "/mydata/json1.json": 232 w.Write([]byte(`{ 233 "employees": [ 234 { 235 "firstName": "John", 236 "lastName": "Doe" 237 }, 238 { 239 "firstName": "Anna", 240 "lastName": "Smith" 241 }, 242 { 243 "firstName": "Peter", 244 "lastName": "Jones" 245 } 246 ] 247 }`)) 248 return 249 250 case "/mydata/xml1.xml": 251 w.Write([]byte(` 252 <hello> 253 <world>Hugo Rocks!</<world> 254 </hello>`)) 255 return 256 257 case "/mydata/svg1.svg": 258 w.Header().Set("Content-Disposition", `attachment; filename="image.svg"`) 259 w.Write([]byte(` 260 <svg height="100" width="100"> 261 <path d="M1e2 1e2H3e2 2e2z"/> 262 </svg>`)) 263 return 264 265 case "/mydata/html1.html": 266 w.Write([]byte(` 267 <html> 268 <a href=#>Cool</a> 269 </html>`)) 270 return 271 272 case "/authenticated/": 273 w.Header().Set("Content-Type", "text/plain") 274 if r.Header.Get("Authorization") == "Bearer abcd" { 275 w.Write([]byte(`Welcome`)) 276 return 277 } 278 http.Error(w, "Forbidden", http.StatusForbidden) 279 return 280 281 case "/post": 282 w.Header().Set("Content-Type", "text/plain") 283 if r.Method == http.MethodPost { 284 body, err := ioutil.ReadAll(r.Body) 285 if err != nil { 286 http.Error(w, "Internal server error", http.StatusInternalServerError) 287 return 288 } 289 w.Write(body) 290 return 291 } 292 http.Error(w, "Bad request", http.StatusBadRequest) 293 return 294 } 295 296 http.Error(w, "Not found", http.StatusNotFound) 297 return 298 })) 299 t.Cleanup(func() { 300 ts.Close() 301 }) 302 303 tests := []struct { 304 name string 305 shouldRun func() bool 306 prepare func(b *sitesBuilder) 307 verify func(b *sitesBuilder) 308 }{ 309 {"tocss", func() bool { return scss.Supports() }, func(b *sitesBuilder) { 310 b.WithTemplates("home.html", ` 311 {{ $scss := resources.Get "scss/styles2.scss" | toCSS }} 312 {{ $sass := resources.Get "sass/styles3.sass" | toCSS }} 313 {{ $scssCustomTarget := resources.Get "scss/styles2.scss" | toCSS (dict "targetPath" "styles/main.css") }} 314 {{ $scssCustomTargetString := resources.Get "scss/styles2.scss" | toCSS "styles/main.css" }} 315 {{ $scssMin := resources.Get "scss/styles2.scss" | toCSS | minify }} 316 {{ $scssFromTempl := ".{{ .Kind }} { color: blue; }" | resources.FromString "kindofblue.templ" | resources.ExecuteAsTemplate "kindofblue.scss" . | toCSS (dict "targetPath" "styles/templ.css") | minify }} 317 {{ $bundle1 := slice $scssFromTempl $scssMin | resources.Concat "styles/bundle1.css" }} 318 T1: Len Content: {{ len $scss.Content }}|RelPermalink: {{ $scss.RelPermalink }}|Permalink: {{ $scss.Permalink }}|MediaType: {{ $scss.MediaType.Type }} 319 T2: Content: {{ $scssMin.Content }}|RelPermalink: {{ $scssMin.RelPermalink }} 320 T3: Content: {{ len $scssCustomTarget.Content }}|RelPermalink: {{ $scssCustomTarget.RelPermalink }}|MediaType: {{ $scssCustomTarget.MediaType.Type }} 321 T4: Content: {{ len $scssCustomTargetString.Content }}|RelPermalink: {{ $scssCustomTargetString.RelPermalink }}|MediaType: {{ $scssCustomTargetString.MediaType.Type }} 322 T5: Content: {{ $sass.Content }}|T5 RelPermalink: {{ $sass.RelPermalink }}| 323 T6: {{ $bundle1.Permalink }} 324 `) 325 }, func(b *sitesBuilder) { 326 b.AssertFileContent("public/index.html", `T1: Len Content: 24|RelPermalink: /scss/styles2.css|Permalink: http://example.com/scss/styles2.css|MediaType: text/css`) 327 b.AssertFileContent("public/index.html", `T2: Content: body{color:#333}|RelPermalink: /scss/styles2.min.css`) 328 b.AssertFileContent("public/index.html", `T3: Content: 24|RelPermalink: /styles/main.css|MediaType: text/css`) 329 b.AssertFileContent("public/index.html", `T4: Content: 24|RelPermalink: /styles/main.css|MediaType: text/css`) 330 b.AssertFileContent("public/index.html", `T5: Content: .content-navigation {`) 331 b.AssertFileContent("public/index.html", `T5 RelPermalink: /sass/styles3.css|`) 332 b.AssertFileContent("public/index.html", `T6: http://example.com/styles/bundle1.css`) 333 334 c.Assert(b.CheckExists("public/styles/templ.min.css"), qt.Equals, false) 335 b.AssertFileContent("public/styles/bundle1.css", `.home{color:blue}body{color:#333}`) 336 }}, 337 338 {"minify", func() bool { return true }, func(b *sitesBuilder) { 339 b.WithConfigFile("toml", `[minify] 340 [minify.tdewolff] 341 [minify.tdewolff.html] 342 keepWhitespace = false 343 `) 344 b.WithTemplates("home.html", fmt.Sprintf(` 345 Min CSS: {{ ( resources.Get "css/styles1.css" | minify ).Content }} 346 Min CSS Remote: {{ ( resources.GetRemote "%[1]s/css/styles1.css" | minify ).Content }} 347 Min JS: {{ ( resources.Get "js/script1.js" | resources.Minify ).Content | safeJS }} 348 Min JS Remote: {{ ( resources.GetRemote "%[1]s/js/script1.js" | minify ).Content }} 349 Min JSON: {{ ( resources.Get "mydata/json1.json" | resources.Minify ).Content | safeHTML }} 350 Min JSON Remote: {{ ( resources.GetRemote "%[1]s/mydata/json1.json" | resources.Minify ).Content | safeHTML }} 351 Min XML: {{ ( resources.Get "mydata/xml1.xml" | resources.Minify ).Content | safeHTML }} 352 Min XML Remote: {{ ( resources.GetRemote "%[1]s/mydata/xml1.xml" | resources.Minify ).Content | safeHTML }} 353 Min SVG: {{ ( resources.Get "mydata/svg1.svg" | resources.Minify ).Content | safeHTML }} 354 Min SVG Remote: {{ ( resources.GetRemote "%[1]s/mydata/svg1.svg" | resources.Minify ).Content | safeHTML }} 355 Min SVG again: {{ ( resources.Get "mydata/svg1.svg" | resources.Minify ).Content | safeHTML }} 356 Min HTML: {{ ( resources.Get "mydata/html1.html" | resources.Minify ).Content | safeHTML }} 357 Min HTML Remote: {{ ( resources.GetRemote "%[1]s/mydata/html1.html" | resources.Minify ).Content | safeHTML }} 358 `, ts.URL)) 359 }, func(b *sitesBuilder) { 360 b.AssertFileContent("public/index.html", `Min CSS: h1{font-style:bold}`) 361 b.AssertFileContent("public/index.html", `Min CSS Remote: h1{font-style:bold}`) 362 b.AssertFileContent("public/index.html", `Min JS: var x=5;document.getElementById("demo").innerHTML=x*10`) 363 b.AssertFileContent("public/index.html", `Min JS Remote: var x=5;document.getElementById("demo").innerHTML=x*10`) 364 b.AssertFileContent("public/index.html", `Min JSON: {"employees":[{"firstName":"John","lastName":"Doe"},{"firstName":"Anna","lastName":"Smith"},{"firstName":"Peter","lastName":"Jones"}]}`) 365 b.AssertFileContent("public/index.html", `Min JSON Remote: {"employees":[{"firstName":"John","lastName":"Doe"},{"firstName":"Anna","lastName":"Smith"},{"firstName":"Peter","lastName":"Jones"}]}`) 366 b.AssertFileContent("public/index.html", `Min XML: <hello><world>Hugo Rocks!</<world></hello>`) 367 b.AssertFileContent("public/index.html", `Min XML Remote: <hello><world>Hugo Rocks!</<world></hello>`) 368 b.AssertFileContent("public/index.html", `Min SVG: <svg height="100" width="100"><path d="M1e2 1e2H3e2 2e2z"/></svg>`) 369 b.AssertFileContent("public/index.html", `Min SVG Remote: <svg height="100" width="100"><path d="M1e2 1e2H3e2 2e2z"/></svg>`) 370 b.AssertFileContent("public/index.html", `Min SVG again: <svg height="100" width="100"><path d="M1e2 1e2H3e2 2e2z"/></svg>`) 371 b.AssertFileContent("public/index.html", `Min HTML: <html><a href=#>Cool</a></html>`) 372 b.AssertFileContent("public/index.html", `Min HTML Remote: <html><a href=#>Cool</a></html>`) 373 }}, 374 375 {"remote", func() bool { return true }, func(b *sitesBuilder) { 376 b.WithTemplates("home.html", fmt.Sprintf(` 377 {{$js := resources.GetRemote "%[1]s/js/script1.js" }} 378 Remote Filename: {{ $js.RelPermalink }} 379 {{$svg := resources.GetRemote "%[1]s/mydata/svg1.svg" }} 380 Remote Content-Disposition: {{ $svg.RelPermalink }} 381 {{$auth := resources.GetRemote "%[1]s/authenticated/" (dict "headers" (dict "Authorization" "Bearer abcd")) }} 382 Remote Authorization: {{ $auth.Content }} 383 {{$post := resources.GetRemote "%[1]s/post" (dict "method" "post" "body" "Request body") }} 384 Remote POST: {{ $post.Content }} 385 `, ts.URL)) 386 }, func(b *sitesBuilder) { 387 b.AssertFileContent("public/index.html", `Remote Filename: /script1_`) 388 b.AssertFileContent("public/index.html", `Remote Content-Disposition: /image_`) 389 b.AssertFileContent("public/index.html", `Remote Authorization: Welcome`) 390 b.AssertFileContent("public/index.html", `Remote POST: Request body`) 391 }}, 392 393 {"concat", func() bool { return true }, func(b *sitesBuilder) { 394 b.WithTemplates("home.html", ` 395 {{ $a := "A" | resources.FromString "a.txt"}} 396 {{ $b := "B" | resources.FromString "b.txt"}} 397 {{ $c := "C" | resources.FromString "c.txt"}} 398 {{ $textResources := .Resources.Match "*.txt" }} 399 {{ $combined := slice $a $b $c | resources.Concat "bundle/concat.txt" }} 400 T1: Content: {{ $combined.Content }}|RelPermalink: {{ $combined.RelPermalink }}|Permalink: {{ $combined.Permalink }}|MediaType: {{ $combined.MediaType.Type }} 401 {{ with $textResources }} 402 {{ $combinedText := . | resources.Concat "bundle/concattxt.txt" }} 403 T2: Content: {{ $combinedText.Content }}|{{ $combinedText.RelPermalink }} 404 {{ end }} 405 {{/* https://github.com/gohugoio/hugo/issues/5269 */}} 406 {{ $css := "body { color: blue; }" | resources.FromString "styles.css" }} 407 {{ $minified := resources.Get "css/styles1.css" | minify }} 408 {{ slice $css $minified | resources.Concat "bundle/mixed.css" }} 409 {{/* https://github.com/gohugoio/hugo/issues/5403 */}} 410 {{ $d := "function D {} // A comment" | resources.FromString "d.js"}} 411 {{ $e := "(function E {})" | resources.FromString "e.js"}} 412 {{ $f := "(function F {})()" | resources.FromString "f.js"}} 413 {{ $jsResources := .Resources.Match "*.js" }} 414 {{ $combinedJs := slice $d $e $f | resources.Concat "bundle/concatjs.js" }} 415 T3: Content: {{ $combinedJs.Content }}|{{ $combinedJs.RelPermalink }} 416 `) 417 }, func(b *sitesBuilder) { 418 b.AssertFileContent("public/index.html", `T1: Content: ABC|RelPermalink: /bundle/concat.txt|Permalink: http://example.com/bundle/concat.txt|MediaType: text/plain`) 419 b.AssertFileContent("public/bundle/concat.txt", "ABC") 420 421 b.AssertFileContent("public/index.html", `T2: Content: t1t|t2t|`) 422 b.AssertFileContent("public/bundle/concattxt.txt", "t1t|t2t|") 423 424 b.AssertFileContent("public/index.html", `T3: Content: function D {} // A comment 425 ; 426 (function E {}) 427 ; 428 (function F {})()|`) 429 b.AssertFileContent("public/bundle/concatjs.js", `function D {} // A comment 430 ; 431 (function E {}) 432 ; 433 (function F {})()`) 434 }}, 435 436 {"concat and fingerprint", func() bool { return true }, func(b *sitesBuilder) { 437 b.WithTemplates("home.html", ` 438 {{ $a := "A" | resources.FromString "a.txt"}} 439 {{ $b := "B" | resources.FromString "b.txt"}} 440 {{ $c := "C" | resources.FromString "c.txt"}} 441 {{ $combined := slice $a $b $c | resources.Concat "bundle/concat.txt" }} 442 {{ $fingerprinted := $combined | fingerprint }} 443 Fingerprinted: {{ $fingerprinted.RelPermalink }} 444 `) 445 }, func(b *sitesBuilder) { 446 b.AssertFileContent("public/index.html", "Fingerprinted: /bundle/concat.b5d4045c3f466fa91fe2cc6abe79232a1a57cdf104f7a26e716e0a1e2789df78.txt") 447 b.AssertFileContent("public/bundle/concat.b5d4045c3f466fa91fe2cc6abe79232a1a57cdf104f7a26e716e0a1e2789df78.txt", "ABC") 448 }}, 449 450 {"fromstring", func() bool { return true }, func(b *sitesBuilder) { 451 b.WithTemplates("home.html", ` 452 {{ $r := "Hugo Rocks!" | resources.FromString "rocks/hugo.txt" }} 453 {{ $r.Content }}|{{ $r.RelPermalink }}|{{ $r.Permalink }}|{{ $r.MediaType.Type }} 454 `) 455 }, func(b *sitesBuilder) { 456 b.AssertFileContent("public/index.html", `Hugo Rocks!|/rocks/hugo.txt|http://example.com/rocks/hugo.txt|text/plain`) 457 b.AssertFileContent("public/rocks/hugo.txt", "Hugo Rocks!") 458 }}, 459 {"execute-as-template", func() bool { 460 return true 461 }, func(b *sitesBuilder) { 462 b.WithTemplates("home.html", ` 463 {{ $var := "Hugo Page" }} 464 {{ if .IsHome }} 465 {{ $var = "Hugo Home" }} 466 {{ end }} 467 T1: {{ $var }} 468 {{ $result := "{{ .Kind | upper }}" | resources.FromString "mytpl.txt" | resources.ExecuteAsTemplate "result.txt" . }} 469 T2: {{ $result.Content }}|{{ $result.RelPermalink}}|{{$result.MediaType.Type }} 470 `) 471 }, func(b *sitesBuilder) { 472 b.AssertFileContent("public/index.html", `T2: HOME|/result.txt|text/plain`, `T1: Hugo Home`) 473 }}, 474 {"fingerprint", func() bool { return true }, func(b *sitesBuilder) { 475 b.WithTemplates("home.html", ` 476 {{ $r := "ab" | resources.FromString "rocks/hugo.txt" }} 477 {{ $result := $r | fingerprint }} 478 {{ $result512 := $r | fingerprint "sha512" }} 479 {{ $resultMD5 := $r | fingerprint "md5" }} 480 T1: {{ $result.Content }}|{{ $result.RelPermalink}}|{{$result.MediaType.Type }}|{{ $result.Data.Integrity }}| 481 T2: {{ $result512.Content }}|{{ $result512.RelPermalink}}|{{$result512.MediaType.Type }}|{{ $result512.Data.Integrity }}| 482 T3: {{ $resultMD5.Content }}|{{ $resultMD5.RelPermalink}}|{{$resultMD5.MediaType.Type }}|{{ $resultMD5.Data.Integrity }}| 483 {{ $r2 := "bc" | resources.FromString "rocks/hugo2.txt" | fingerprint }} 484 {{/* https://github.com/gohugoio/hugo/issues/5296 */}} 485 T4: {{ $r2.Data.Integrity }}| 486 487 488 `) 489 }, func(b *sitesBuilder) { 490 b.AssertFileContent("public/index.html", `T1: ab|/rocks/hugo.fb8e20fc2e4c3f248c60c39bd652f3c1347298bb977b8b4d5903b85055620603.txt|text/plain|sha256-+44g/C5MPySMYMOb1lLzwTRymLuXe4tNWQO4UFViBgM=|`) 491 b.AssertFileContent("public/index.html", `T2: ab|/rocks/hugo.2d408a0717ec188158278a796c689044361dc6fdde28d6f04973b80896e1823975cdbf12eb63f9e0591328ee235d80e9b5bf1aa6a44f4617ff3caf6400eb172d.txt|text/plain|sha512-LUCKBxfsGIFYJ4p5bGiQRDYdxv3eKNbwSXO4CJbhgjl1zb8S62P54FkTKO4jXYDptb8apqRPRhf/PK9kAOsXLQ==|`) 492 b.AssertFileContent("public/index.html", `T3: ab|/rocks/hugo.187ef4436122d1cc2f40dc2b92f0eba0.txt|text/plain|md5-GH70Q2Ei0cwvQNwrkvDroA==|`) 493 b.AssertFileContent("public/index.html", `T4: sha256-Hgu9bGhroFC46wP/7txk/cnYCUf86CGrvl1tyNJSxaw=|`) 494 }}, 495 // https://github.com/gohugoio/hugo/issues/5226 496 {"baseurl-path", func() bool { return true }, func(b *sitesBuilder) { 497 b.WithSimpleConfigFileAndBaseURL("https://example.com/hugo/") 498 b.WithTemplates("home.html", ` 499 {{ $r1 := "ab" | resources.FromString "rocks/hugo.txt" }} 500 T1: {{ $r1.Permalink }}|{{ $r1.RelPermalink }} 501 `) 502 }, func(b *sitesBuilder) { 503 b.AssertFileContent("public/index.html", `T1: https://example.com/hugo/rocks/hugo.txt|/hugo/rocks/hugo.txt`) 504 }}, 505 506 // https://github.com/gohugoio/hugo/issues/4944 507 {"Prevent resource publish on .Content only", func() bool { return true }, func(b *sitesBuilder) { 508 b.WithTemplates("home.html", ` 509 {{ $cssInline := "body { color: green; }" | resources.FromString "inline.css" | minify }} 510 {{ $cssPublish1 := "body { color: blue; }" | resources.FromString "external1.css" | minify }} 511 {{ $cssPublish2 := "body { color: orange; }" | resources.FromString "external2.css" | minify }} 512 513 Inline: {{ $cssInline.Content }} 514 Publish 1: {{ $cssPublish1.Content }} {{ $cssPublish1.RelPermalink }} 515 Publish 2: {{ $cssPublish2.Permalink }} 516 `) 517 }, func(b *sitesBuilder) { 518 b.AssertFileContent("public/index.html", 519 `Inline: body{color:green}`, 520 "Publish 1: body{color:blue} /external1.min.css", 521 "Publish 2: http://example.com/external2.min.css", 522 ) 523 b.Assert(b.CheckExists("public/external2.css"), qt.Equals, false) 524 b.Assert(b.CheckExists("public/external1.css"), qt.Equals, false) 525 b.Assert(b.CheckExists("public/external2.min.css"), qt.Equals, true) 526 b.Assert(b.CheckExists("public/external1.min.css"), qt.Equals, true) 527 b.Assert(b.CheckExists("public/inline.min.css"), qt.Equals, false) 528 }}, 529 530 {"unmarshal", func() bool { return true }, func(b *sitesBuilder) { 531 b.WithTemplates("home.html", ` 532 {{ $toml := "slogan = \"Hugo Rocks!\"" | resources.FromString "slogan.toml" | transform.Unmarshal }} 533 {{ $csv1 := "\"Hugo Rocks\",\"Hugo is Fast!\"" | resources.FromString "slogans.csv" | transform.Unmarshal }} 534 {{ $csv2 := "a;b;c" | transform.Unmarshal (dict "delimiter" ";") }} 535 {{ $xml := "<?xml version=\"1.0\" encoding=\"UTF-8\"?><note><to>You</to><from>Me</from><heading>Reminder</heading><body>Do not forget XML</body></note>" | transform.Unmarshal }} 536 537 Slogan: {{ $toml.slogan }} 538 CSV1: {{ $csv1 }} {{ len (index $csv1 0) }} 539 CSV2: {{ $csv2 }} 540 XML: {{ $xml.body }} 541 `) 542 }, func(b *sitesBuilder) { 543 b.AssertFileContent("public/index.html", 544 `Slogan: Hugo Rocks!`, 545 `[[Hugo Rocks Hugo is Fast!]] 2`, 546 `CSV2: [[a b c]]`, 547 `XML: Do not forget XML`, 548 ) 549 }}, 550 {"resources.Get", func() bool { return true }, func(b *sitesBuilder) { 551 b.WithTemplates("home.html", `NOT FOUND: {{ if (resources.Get "this-does-not-exist") }}FAILED{{ else }}OK{{ end }}`) 552 }, func(b *sitesBuilder) { 553 b.AssertFileContent("public/index.html", "NOT FOUND: OK") 554 }}, 555 556 {"template", func() bool { return true }, func(b *sitesBuilder) {}, func(b *sitesBuilder) { 557 }}, 558 } 559 560 for _, test := range tests { 561 test := test 562 t.Run(test.name, func(t *testing.T) { 563 if !test.shouldRun() { 564 t.Skip() 565 } 566 t.Parallel() 567 568 b := newTestSitesBuilder(t).WithLogger(loggers.NewErrorLogger()) 569 b.WithContent("_index.md", ` 570 --- 571 title: Home 572 --- 573 574 Home. 575 576 `, 577 "page1.md", ` 578 --- 579 title: Hello1 580 --- 581 582 Hello1 583 `, 584 "page2.md", ` 585 --- 586 title: Hello2 587 --- 588 589 Hello2 590 `, 591 "t1.txt", "t1t|", 592 "t2.txt", "t2t|", 593 ) 594 595 b.WithSourceFile(filepath.Join("assets", "css", "styles1.css"), ` 596 h1 { 597 font-style: bold; 598 } 599 `) 600 601 b.WithSourceFile(filepath.Join("assets", "js", "script1.js"), ` 602 var x; 603 x = 5; 604 document.getElementById("demo").innerHTML = x * 10; 605 `) 606 607 b.WithSourceFile(filepath.Join("assets", "mydata", "json1.json"), ` 608 { 609 "employees":[ 610 {"firstName":"John", "lastName":"Doe"}, 611 {"firstName":"Anna", "lastName":"Smith"}, 612 {"firstName":"Peter", "lastName":"Jones"} 613 ] 614 } 615 `) 616 617 b.WithSourceFile(filepath.Join("assets", "mydata", "svg1.svg"), ` 618 <svg height="100" width="100"> 619 <path d="M 100 100 L 300 100 L 200 100 z"/> 620 </svg> 621 `) 622 623 b.WithSourceFile(filepath.Join("assets", "mydata", "xml1.xml"), ` 624 <hello> 625 <world>Hugo Rocks!</<world> 626 </hello> 627 `) 628 629 b.WithSourceFile(filepath.Join("assets", "mydata", "html1.html"), ` 630 <html> 631 <a href="#"> 632 Cool 633 </a > 634 </html> 635 `) 636 637 b.WithSourceFile(filepath.Join("assets", "scss", "styles2.scss"), ` 638 $color: #333; 639 640 body { 641 color: $color; 642 } 643 `) 644 645 b.WithSourceFile(filepath.Join("assets", "sass", "styles3.sass"), ` 646 $color: #333; 647 648 .content-navigation 649 border-color: $color 650 651 `) 652 653 test.prepare(b) 654 b.Build(BuildCfg{}) 655 test.verify(b) 656 }) 657 } 658 } 659 660 func TestMultiSiteResource(t *testing.T) { 661 t.Parallel() 662 c := qt.New(t) 663 664 b := newMultiSiteTestDefaultBuilder(t) 665 666 b.CreateSites().Build(BuildCfg{}) 667 668 // This build is multilingual, but not multihost. There should be only one pipes.txt 669 b.AssertFileContent("public/fr/index.html", "French Home Page", "String Resource: /blog/text/pipes.txt") 670 c.Assert(b.CheckExists("public/fr/text/pipes.txt"), qt.Equals, false) 671 c.Assert(b.CheckExists("public/en/text/pipes.txt"), qt.Equals, false) 672 b.AssertFileContent("public/en/index.html", "Default Home Page", "String Resource: /blog/text/pipes.txt") 673 b.AssertFileContent("public/text/pipes.txt", "Hugo Pipes") 674 } 675 676 func TestResourcesMatch(t *testing.T) { 677 t.Parallel() 678 679 b := newTestSitesBuilder(t) 680 681 b.WithContent("page.md", "") 682 683 b.WithSourceFile( 684 "assets/jsons/data1.json", "json1 content", 685 "assets/jsons/data2.json", "json2 content", 686 "assets/jsons/data3.xml", "xml content", 687 ) 688 689 b.WithTemplates("index.html", ` 690 {{ $jsons := (resources.Match "jsons/*.json") }} 691 {{ $json := (resources.GetMatch "jsons/*.json") }} 692 {{ printf "JSONS: %d" (len $jsons) }} 693 JSON: {{ $json.RelPermalink }}: {{ $json.Content }} 694 {{ range $jsons }} 695 {{- .RelPermalink }}: {{ .Content }} 696 {{ end }} 697 `) 698 699 b.Build(BuildCfg{}) 700 701 b.AssertFileContent("public/index.html", 702 "JSON: /jsons/data1.json: json1 content", 703 "JSONS: 2", "/jsons/data1.json: json1 content") 704 } 705 706 func TestResourceMinifyDisabled(t *testing.T) { 707 t.Parallel() 708 709 b := newTestSitesBuilder(t).WithConfigFile("toml", ` 710 baseURL = "https://example.org" 711 712 [minify] 713 disableXML=true 714 715 716 `) 717 718 b.WithContent("page.md", "") 719 720 b.WithSourceFile( 721 "assets/xml/data.xml", "<root> <foo> asdfasdf </foo> </root>", 722 ) 723 724 b.WithTemplates("index.html", ` 725 {{ $xml := resources.Get "xml/data.xml" | minify | fingerprint }} 726 XML: {{ $xml.Content | safeHTML }}|{{ $xml.RelPermalink }} 727 `) 728 729 b.Build(BuildCfg{}) 730 731 b.AssertFileContent("public/index.html", ` 732 XML: <root> <foo> asdfasdf </foo> </root>|/xml/data.min.3be4fddd19aaebb18c48dd6645215b822df74701957d6d36e59f203f9c30fd9f.xml 733 `) 734 }