hugo

Fork of github.com/gohugoio/hugo with reverse pagination support

git clone git://git.shimmy1996.com/hugo.git

resource_chain_test.go (29570B)

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