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&#43;8= /styles.min.a1df58687c3c9cc38bf26532f7b4b2f2c2b0315dcde212376959995c04f11fef.css
   100  CSS integrity Data last:  /styles2.min.1cfc52986836405d37f9998a63fd6dd8608e8c410e5e3db1daaa30f78bc273ba.css sha256-HPxSmGg2QF03&#43;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(&#34;demo&#34;).innerHTML=x*10`)
   363  			b.AssertFileContent("public/index.html", `Min JS Remote: var x=5;document.getElementById(&#34;demo&#34;).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-&#43;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  }