github.com/gohugoio/hugo@v0.88.1/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  	"bytes"
    18  	"fmt"
    19  	"io"
    20  	"math/rand"
    21  	"os"
    22  
    23  	"github.com/gohugoio/hugo/config"
    24  
    25  	"github.com/gohugoio/hugo/resources/resource_transformers/tocss/dartsass"
    26  
    27  	"path/filepath"
    28  	"strings"
    29  	"testing"
    30  	"time"
    31  
    32  	"github.com/gohugoio/hugo/common/hexec"
    33  
    34  	jww "github.com/spf13/jwalterweatherman"
    35  
    36  	"github.com/gohugoio/hugo/common/herrors"
    37  
    38  	"github.com/gohugoio/hugo/htesting"
    39  
    40  	qt "github.com/frankban/quicktest"
    41  
    42  	"github.com/gohugoio/hugo/hugofs"
    43  
    44  	"github.com/gohugoio/hugo/common/loggers"
    45  	"github.com/gohugoio/hugo/resources/resource_transformers/tocss/scss"
    46  )
    47  
    48  func TestSCSSWithIncludePaths(t *testing.T) {
    49  	c := qt.New(t)
    50  
    51  	for _, test := range []struct {
    52  		name     string
    53  		supports func() bool
    54  	}{
    55  		{"libsass", func() bool { return scss.Supports() }},
    56  		{"dartsass", func() bool { return dartsass.Supports() }},
    57  	} {
    58  
    59  		c.Run(test.name, func(c *qt.C) {
    60  			if !test.supports() {
    61  				c.Skip(fmt.Sprintf("Skip %s", test.name))
    62  			}
    63  
    64  			workDir, clean, err := htesting.CreateTempDir(hugofs.Os, fmt.Sprintf("hugo-scss-include-%s", test.name))
    65  			c.Assert(err, qt.IsNil)
    66  			defer clean()
    67  
    68  			v := config.New()
    69  			v.Set("workingDir", workDir)
    70  			b := newTestSitesBuilder(c).WithLogger(loggers.NewErrorLogger())
    71  			// Need to use OS fs for this.
    72  			b.Fs = hugofs.NewDefault(v)
    73  			b.WithWorkingDir(workDir)
    74  			b.WithViper(v)
    75  
    76  			fooDir := filepath.Join(workDir, "node_modules", "foo")
    77  			scssDir := filepath.Join(workDir, "assets", "scss")
    78  			c.Assert(os.MkdirAll(fooDir, 0777), qt.IsNil)
    79  			c.Assert(os.MkdirAll(filepath.Join(workDir, "content", "sect"), 0777), qt.IsNil)
    80  			c.Assert(os.MkdirAll(filepath.Join(workDir, "data"), 0777), qt.IsNil)
    81  			c.Assert(os.MkdirAll(filepath.Join(workDir, "i18n"), 0777), qt.IsNil)
    82  			c.Assert(os.MkdirAll(filepath.Join(workDir, "layouts", "shortcodes"), 0777), qt.IsNil)
    83  			c.Assert(os.MkdirAll(filepath.Join(workDir, "layouts", "_default"), 0777), qt.IsNil)
    84  			c.Assert(os.MkdirAll(filepath.Join(scssDir), 0777), qt.IsNil)
    85  
    86  			b.WithSourceFile(filepath.Join(fooDir, "_moo.scss"), `
    87  $moolor: #fff;
    88  
    89  moo {
    90    color: $moolor;
    91  }
    92  `)
    93  
    94  			b.WithSourceFile(filepath.Join(scssDir, "main.scss"), `
    95  @import "moo";
    96  
    97  `)
    98  
    99  			b.WithTemplatesAdded("index.html", fmt.Sprintf(`
   100  {{ $cssOpts := (dict "includePaths" (slice "node_modules/foo") "transpiler" %q ) }}
   101  {{ $r := resources.Get "scss/main.scss" |  toCSS $cssOpts  | minify  }}
   102  T1: {{ $r.Content }}
   103  `, test.name))
   104  			b.Build(BuildCfg{})
   105  
   106  			b.AssertFileContent(filepath.Join(workDir, "public/index.html"), `T1: moo{color:#fff}`)
   107  		})
   108  
   109  	}
   110  
   111  }
   112  
   113  func TestSCSSWithRegularCSSImport(t *testing.T) {
   114  	c := qt.New(t)
   115  
   116  	for _, test := range []struct {
   117  		name     string
   118  		supports func() bool
   119  	}{
   120  		{"libsass", func() bool { return scss.Supports() }},
   121  		{"dartsass", func() bool { return dartsass.Supports() }},
   122  	} {
   123  
   124  		c.Run(test.name, func(c *qt.C) {
   125  			if !test.supports() {
   126  				c.Skip(fmt.Sprintf("Skip %s", test.name))
   127  			}
   128  
   129  			workDir, clean, err := htesting.CreateTempDir(hugofs.Os, fmt.Sprintf("hugo-scss-include-regular-%s", test.name))
   130  			c.Assert(err, qt.IsNil)
   131  			defer clean()
   132  
   133  			v := config.New()
   134  			v.Set("workingDir", workDir)
   135  			b := newTestSitesBuilder(c).WithLogger(loggers.NewErrorLogger())
   136  			// Need to use OS fs for this.
   137  			b.Fs = hugofs.NewDefault(v)
   138  			b.WithWorkingDir(workDir)
   139  			b.WithViper(v)
   140  
   141  			scssDir := filepath.Join(workDir, "assets", "scss")
   142  			c.Assert(os.MkdirAll(filepath.Join(workDir, "content", "sect"), 0777), qt.IsNil)
   143  			c.Assert(os.MkdirAll(filepath.Join(workDir, "data"), 0777), qt.IsNil)
   144  			c.Assert(os.MkdirAll(filepath.Join(workDir, "i18n"), 0777), qt.IsNil)
   145  			c.Assert(os.MkdirAll(filepath.Join(workDir, "layouts", "shortcodes"), 0777), qt.IsNil)
   146  			c.Assert(os.MkdirAll(filepath.Join(workDir, "layouts", "_default"), 0777), qt.IsNil)
   147  			c.Assert(os.MkdirAll(filepath.Join(scssDir), 0777), qt.IsNil)
   148  			b.WithSourceFile(filepath.Join(scssDir, "regular.css"), ``)
   149  			b.WithSourceFile(filepath.Join(scssDir, "another.css"), ``)
   150  			b.WithSourceFile(filepath.Join(scssDir, "_moo.scss"), `
   151  $moolor: #fff;
   152  
   153  moo {
   154    color: $moolor;
   155  }
   156  `)
   157  
   158  			b.WithSourceFile(filepath.Join(scssDir, "main.scss"), `
   159  @import "moo";
   160  @import "regular.css";
   161  @import "moo";
   162  @import "another.css";
   163  
   164  /* foo */
   165  `)
   166  
   167  			b.WithTemplatesAdded("index.html", fmt.Sprintf(`
   168  {{ $r := resources.Get "scss/main.scss" |  toCSS (dict "transpiler" %q)  }}
   169  T1: {{ $r.Content | safeHTML }}
   170  `, test.name))
   171  			b.Build(BuildCfg{})
   172  
   173  			if test.name == "libsass" {
   174  				// LibSass does not support regular CSS imports. There
   175  				// is an open bug about it that probably will never be resolved.
   176  				// Hugo works around this by preserving them in place:
   177  				b.AssertFileContent(filepath.Join(workDir, "public/index.html"), `
   178   T1: moo {
   179   color: #fff; }
   180  
   181  @import "regular.css";
   182  moo {
   183   color: #fff; }
   184  
   185  @import "another.css";
   186  /* foo */
   187          
   188  `)
   189  			} else {
   190  				// Dart Sass does not follow regular CSS import, but they
   191  				// get pulled to the top.
   192  				b.AssertFileContent(filepath.Join(workDir, "public/index.html"), `T1: @import "regular.css";
   193  @import "another.css";
   194  moo {
   195    color: #fff;
   196  }
   197  
   198  moo {
   199    color: #fff;
   200  }
   201  
   202  /* foo */`)
   203  
   204  			}
   205  		})
   206  	}
   207  
   208  }
   209  
   210  func TestSCSSWithThemeOverrides(t *testing.T) {
   211  	c := qt.New(t)
   212  
   213  	for _, test := range []struct {
   214  		name     string
   215  		supports func() bool
   216  	}{
   217  		{"libsass", func() bool { return scss.Supports() }},
   218  		{"dartsass", func() bool { return dartsass.Supports() }},
   219  	} {
   220  
   221  		c.Run(test.name, func(c *qt.C) {
   222  			if !test.supports() {
   223  				c.Skip(fmt.Sprintf("Skip %s", test.name))
   224  			}
   225  
   226  			workDir, clean1, err := htesting.CreateTempDir(hugofs.Os, fmt.Sprintf("hugo-scss-include-theme-overrides-%s", test.name))
   227  			c.Assert(err, qt.IsNil)
   228  			defer clean1()
   229  
   230  			theme := "mytheme"
   231  			themesDir := filepath.Join(workDir, "themes")
   232  			themeDirs := filepath.Join(themesDir, theme)
   233  			v := config.New()
   234  			v.Set("workingDir", workDir)
   235  			v.Set("theme", theme)
   236  			b := newTestSitesBuilder(c).WithLogger(loggers.NewErrorLogger())
   237  			// Need to use OS fs for this.
   238  			b.Fs = hugofs.NewDefault(v)
   239  			b.WithWorkingDir(workDir)
   240  			b.WithViper(v)
   241  
   242  			fooDir := filepath.Join(workDir, "node_modules", "foo")
   243  			scssDir := filepath.Join(workDir, "assets", "scss")
   244  			scssThemeDir := filepath.Join(themeDirs, "assets", "scss")
   245  			c.Assert(os.MkdirAll(fooDir, 0777), qt.IsNil)
   246  			c.Assert(os.MkdirAll(filepath.Join(workDir, "content", "sect"), 0777), qt.IsNil)
   247  			c.Assert(os.MkdirAll(filepath.Join(workDir, "data"), 0777), qt.IsNil)
   248  			c.Assert(os.MkdirAll(filepath.Join(workDir, "i18n"), 0777), qt.IsNil)
   249  			c.Assert(os.MkdirAll(filepath.Join(workDir, "layouts", "shortcodes"), 0777), qt.IsNil)
   250  			c.Assert(os.MkdirAll(filepath.Join(workDir, "layouts", "_default"), 0777), qt.IsNil)
   251  			c.Assert(os.MkdirAll(filepath.Join(scssDir, "components"), 0777), qt.IsNil)
   252  			c.Assert(os.MkdirAll(filepath.Join(scssThemeDir, "components"), 0777), qt.IsNil)
   253  
   254  			b.WithSourceFile(filepath.Join(scssThemeDir, "components", "_imports.scss"), `
   255  @import "moo";
   256  @import "_boo";
   257  @import "_zoo";
   258  
   259  `)
   260  
   261  			b.WithSourceFile(filepath.Join(scssThemeDir, "components", "_moo.scss"), `
   262  $moolor: #fff;
   263  
   264  moo {
   265    color: $moolor;
   266  }
   267  `)
   268  
   269  			// Only in theme.
   270  			b.WithSourceFile(filepath.Join(scssThemeDir, "components", "_zoo.scss"), `
   271  $zoolor: pink;
   272  
   273  zoo {
   274    color: $zoolor;
   275  }
   276  `)
   277  
   278  			b.WithSourceFile(filepath.Join(scssThemeDir, "components", "_boo.scss"), `
   279  $boolor: orange;
   280  
   281  boo {
   282    color: $boolor;
   283  }
   284  `)
   285  
   286  			b.WithSourceFile(filepath.Join(scssThemeDir, "main.scss"), `
   287  @import "components/imports";
   288  
   289  `)
   290  
   291  			b.WithSourceFile(filepath.Join(scssDir, "components", "_moo.scss"), `
   292  $moolor: #ccc;
   293  
   294  moo {
   295    color: $moolor;
   296  }
   297  `)
   298  
   299  			b.WithSourceFile(filepath.Join(scssDir, "components", "_boo.scss"), `
   300  $boolor: green;
   301  
   302  boo {
   303    color: $boolor;
   304  }
   305  `)
   306  
   307  			b.WithTemplatesAdded("index.html", fmt.Sprintf(`
   308  {{ $cssOpts := (dict "includePaths" (slice "node_modules/foo" ) "transpiler" %q ) }}
   309  {{ $r := resources.Get "scss/main.scss" |  toCSS $cssOpts  | minify  }}
   310  T1: {{ $r.Content }}
   311  `, test.name))
   312  			b.Build(BuildCfg{})
   313  
   314  			b.AssertFileContent(
   315  				filepath.Join(workDir, "public/index.html"),
   316  				`T1: moo{color:#ccc}boo{color:green}zoo{color:pink}`,
   317  			)
   318  		})
   319  	}
   320  
   321  }
   322  
   323  // https://github.com/gohugoio/hugo/issues/6274
   324  func TestSCSSWithIncludePathsSass(t *testing.T) {
   325  	c := qt.New(t)
   326  
   327  	for _, test := range []struct {
   328  		name     string
   329  		supports func() bool
   330  	}{
   331  		{"libsass", func() bool { return scss.Supports() }},
   332  		{"dartsass", func() bool { return dartsass.Supports() }},
   333  	} {
   334  
   335  		c.Run(test.name, func(c *qt.C) {
   336  			if !test.supports() {
   337  				c.Skip(fmt.Sprintf("Skip %s", test.name))
   338  			}
   339  		})
   340  	}
   341  	if !scss.Supports() {
   342  		t.Skip("Skip SCSS")
   343  	}
   344  	workDir, clean1, err := htesting.CreateTempDir(hugofs.Os, "hugo-scss-includepaths")
   345  	c.Assert(err, qt.IsNil)
   346  	defer clean1()
   347  
   348  	v := config.New()
   349  	v.Set("workingDir", workDir)
   350  	v.Set("theme", "mytheme")
   351  	b := newTestSitesBuilder(t).WithLogger(loggers.NewErrorLogger())
   352  	// Need to use OS fs for this.
   353  	b.Fs = hugofs.NewDefault(v)
   354  	b.WithWorkingDir(workDir)
   355  	b.WithViper(v)
   356  
   357  	hulmaDir := filepath.Join(workDir, "node_modules", "hulma")
   358  	scssDir := filepath.Join(workDir, "themes/mytheme/assets", "scss")
   359  	c.Assert(os.MkdirAll(hulmaDir, 0777), qt.IsNil)
   360  	c.Assert(os.MkdirAll(scssDir, 0777), qt.IsNil)
   361  
   362  	b.WithSourceFile(filepath.Join(scssDir, "main.scss"), `
   363  @import "hulma/hulma";
   364  
   365  `)
   366  
   367  	b.WithSourceFile(filepath.Join(hulmaDir, "hulma.sass"), `
   368  $hulma: #ccc;
   369  
   370  foo
   371    color: $hulma;
   372  
   373  `)
   374  
   375  	b.WithTemplatesAdded("index.html", `
   376   {{ $scssOptions := (dict "targetPath" "css/styles.css" "enableSourceMap" false "includePaths" (slice "node_modules")) }}
   377  {{ $r := resources.Get "scss/main.scss" |  toCSS $scssOptions  | minify  }}
   378  T1: {{ $r.Content }}
   379  `)
   380  	b.Build(BuildCfg{})
   381  
   382  	b.AssertFileContent(filepath.Join(workDir, "public/index.html"), `T1: foo{color:#ccc}`)
   383  }
   384  
   385  func TestResourceChainBasic(t *testing.T) {
   386  	t.Parallel()
   387  
   388  	b := newTestSitesBuilder(t)
   389  	b.WithTemplatesAdded("index.html", `
   390  {{ $hello := "<h1>     Hello World!   </h1>" | resources.FromString "hello.html" | fingerprint "sha512" | minify  | fingerprint }}
   391  {{ $cssFingerprinted1 := "body {  background-color: lightblue; }" | resources.FromString "styles.css" |  minify  | fingerprint }}
   392  {{ $cssFingerprinted2 := "body {  background-color: orange; }" | resources.FromString "styles2.css" |  minify  | fingerprint }}
   393  
   394  
   395  HELLO: {{ $hello.Name }}|{{ $hello.RelPermalink }}|{{ $hello.Content | safeHTML }}
   396  
   397  {{ $img := resources.Get "images/sunset.jpg" }}
   398  {{ $fit := $img.Fit "200x200" }}
   399  {{ $fit2 := $fit.Fit "100x200" }}
   400  {{ $img = $img | fingerprint }}
   401  SUNSET: {{ $img.Name }}|{{ $img.RelPermalink }}|{{ $img.Width }}|{{ len $img.Content }}
   402  FIT: {{ $fit.Name }}|{{ $fit.RelPermalink }}|{{ $fit.Width }}
   403  CSS integrity Data first: {{ $cssFingerprinted1.Data.Integrity }} {{ $cssFingerprinted1.RelPermalink }}
   404  CSS integrity Data last:  {{ $cssFingerprinted2.RelPermalink }} {{ $cssFingerprinted2.Data.Integrity }}
   405  
   406  `)
   407  
   408  	fs := b.Fs.Source
   409  
   410  	imageDir := filepath.Join("assets", "images")
   411  	b.Assert(os.MkdirAll(imageDir, 0777), qt.IsNil)
   412  	src, err := os.Open("testdata/sunset.jpg")
   413  	b.Assert(err, qt.IsNil)
   414  	out, err := fs.Create(filepath.Join(imageDir, "sunset.jpg"))
   415  	b.Assert(err, qt.IsNil)
   416  	_, err = io.Copy(out, src)
   417  	b.Assert(err, qt.IsNil)
   418  	out.Close()
   419  
   420  	b.Running()
   421  
   422  	for i := 0; i < 2; i++ {
   423  
   424  		b.Build(BuildCfg{})
   425  
   426  		b.AssertFileContent("public/index.html",
   427  			`
   428  SUNSET: images/sunset.jpg|/images/sunset.a9bf1d944e19c0f382e0d8f51de690f7d0bc8fa97390c4242a86c3e5c0737e71.jpg|900|90587
   429  FIT: images/sunset.jpg|/images/sunset_hu59e56ffff1bc1d8d122b1403d34e039f_90587_200x200_fit_q75_box.jpg|200
   430  CSS integrity Data first: sha256-od9YaHw8nMOL8mUy97Sy8sKwMV3N4hI3aVmZXATxH&#43;8= /styles.min.a1df58687c3c9cc38bf26532f7b4b2f2c2b0315dcde212376959995c04f11fef.css
   431  CSS integrity Data last:  /styles2.min.1cfc52986836405d37f9998a63fd6dd8608e8c410e5e3db1daaa30f78bc273ba.css sha256-HPxSmGg2QF03&#43;ZmKY/1t2GCOjEEOXj2x2qow94vCc7o=
   432  `)
   433  
   434  		b.AssertFileContent("public/styles.min.a1df58687c3c9cc38bf26532f7b4b2f2c2b0315dcde212376959995c04f11fef.css", "body{background-color:#add8e6}")
   435  		b.AssertFileContent("public//styles2.min.1cfc52986836405d37f9998a63fd6dd8608e8c410e5e3db1daaa30f78bc273ba.css", "body{background-color:orange}")
   436  
   437  		b.EditFiles("page1.md", `
   438  ---
   439  title: "Page 1 edit"
   440  summary: "Edited summary"
   441  ---
   442  
   443  Edited content.
   444  
   445  `)
   446  
   447  		b.Assert(b.Fs.Destination.Remove("public"), qt.IsNil)
   448  		b.H.ResourceSpec.ClearCaches()
   449  
   450  	}
   451  }
   452  
   453  func TestResourceChainPostProcess(t *testing.T) {
   454  	t.Parallel()
   455  
   456  	rnd := rand.New(rand.NewSource(time.Now().UnixNano()))
   457  
   458  	b := newTestSitesBuilder(t)
   459  	b.WithConfigFile("toml", `[minify]
   460    minifyOutput = true
   461    [minify.tdewolff]
   462      [minify.tdewolff.html]
   463        keepQuotes = false
   464        keepWhitespace = false`)
   465  	b.WithContent("page1.md", "---\ntitle: Page1\n---")
   466  	b.WithContent("page2.md", "---\ntitle: Page2\n---")
   467  
   468  	b.WithTemplates(
   469  		"_default/single.html", `{{ $hello := "<h1>     Hello World!   </h1>" | resources.FromString "hello.html" | minify  | fingerprint "md5" | resources.PostProcess }}
   470  HELLO: {{ $hello.RelPermalink }}	
   471  `,
   472  		"index.html", `Start.
   473  {{ $hello := "<h1>     Hello World!   </h1>" | resources.FromString "hello.html" | minify  | fingerprint "md5" | resources.PostProcess }}
   474  
   475  HELLO: {{ $hello.RelPermalink }}|Integrity: {{ $hello.Data.Integrity }}|MediaType: {{ $hello.MediaType.Type }}
   476  HELLO2: Name: {{ $hello.Name }}|Content: {{ $hello.Content }}|Title: {{ $hello.Title }}|ResourceType: {{ $hello.ResourceType }}
   477  
   478  // Issue #8884
   479  <a href="hugo.rocks">foo</a>
   480  <a href="{{ $hello.RelPermalink }}" integrity="{{ $hello.Data.Integrity}}">Hello</a>
   481  `+strings.Repeat("a b", rnd.Intn(10)+1)+`
   482  
   483  
   484  End.`)
   485  
   486  	b.Running()
   487  	b.Build(BuildCfg{})
   488  	b.AssertFileContent("public/index.html",
   489  		`Start.
   490  HELLO: /hello.min.a2d1cb24f24b322a7dad520414c523e9.html|Integrity: md5-otHLJPJLMip9rVIEFMUj6Q==|MediaType: text/html
   491  HELLO2: Name: hello.html|Content: <h1>Hello World!</h1>|Title: hello.html|ResourceType: text
   492  <a href=hugo.rocks>foo</a>
   493  <a href="/hello.min.a2d1cb24f24b322a7dad520414c523e9.html" integrity="md5-otHLJPJLMip9rVIEFMUj6Q==">Hello</a>
   494  End.`)
   495  
   496  	b.AssertFileContent("public/page1/index.html", `HELLO: /hello.min.a2d1cb24f24b322a7dad520414c523e9.html`)
   497  	b.AssertFileContent("public/page2/index.html", `HELLO: /hello.min.a2d1cb24f24b322a7dad520414c523e9.html`)
   498  }
   499  
   500  func BenchmarkResourceChainPostProcess(b *testing.B) {
   501  	for i := 0; i < b.N; i++ {
   502  		b.StopTimer()
   503  		s := newTestSitesBuilder(b)
   504  		for i := 0; i < 300; i++ {
   505  			s.WithContent(fmt.Sprintf("page%d.md", i+1), "---\ntitle: Page\n---")
   506  		}
   507  		s.WithTemplates("_default/single.html", `Start.
   508  Some text.
   509  
   510  
   511  {{ $hello1 := "<h1>     Hello World 2!   </h1>" | resources.FromString "hello.html" | minify  | fingerprint "md5" | resources.PostProcess }}
   512  {{ $hello2 := "<h1>     Hello World 2!   </h1>" | resources.FromString (printf "%s.html" .Path) | minify  | fingerprint "md5" | resources.PostProcess }}
   513  
   514  Some more text.
   515  
   516  HELLO: {{ $hello1.RelPermalink }}|Integrity: {{ $hello1.Data.Integrity }}|MediaType: {{ $hello1.MediaType.Type }}
   517  
   518  Some more text.
   519  
   520  HELLO2: Name: {{ $hello2.Name }}|Content: {{ $hello2.Content }}|Title: {{ $hello2.Title }}|ResourceType: {{ $hello2.ResourceType }}
   521  
   522  Some more text.
   523  
   524  HELLO2_2: Name: {{ $hello2.Name }}|Content: {{ $hello2.Content }}|Title: {{ $hello2.Title }}|ResourceType: {{ $hello2.ResourceType }}
   525  
   526  End.
   527  `)
   528  
   529  		b.StartTimer()
   530  		s.Build(BuildCfg{})
   531  
   532  	}
   533  }
   534  
   535  func TestResourceChains(t *testing.T) {
   536  	t.Parallel()
   537  
   538  	c := qt.New(t)
   539  
   540  	tests := []struct {
   541  		name      string
   542  		shouldRun func() bool
   543  		prepare   func(b *sitesBuilder)
   544  		verify    func(b *sitesBuilder)
   545  	}{
   546  		{"tocss", func() bool { return scss.Supports() }, func(b *sitesBuilder) {
   547  			b.WithTemplates("home.html", `
   548  {{ $scss := resources.Get "scss/styles2.scss" | toCSS }}
   549  {{ $sass := resources.Get "sass/styles3.sass" | toCSS }}
   550  {{ $scssCustomTarget := resources.Get "scss/styles2.scss" | toCSS (dict "targetPath" "styles/main.css") }}
   551  {{ $scssCustomTargetString := resources.Get "scss/styles2.scss" | toCSS "styles/main.css" }}
   552  {{ $scssMin := resources.Get "scss/styles2.scss" | toCSS | minify  }}
   553  {{  $scssFromTempl :=  ".{{ .Kind }} { color: blue; }" | resources.FromString "kindofblue.templ"  | resources.ExecuteAsTemplate "kindofblue.scss" . | toCSS (dict "targetPath" "styles/templ.css") | minify }}
   554  {{ $bundle1 := slice $scssFromTempl $scssMin  | resources.Concat "styles/bundle1.css" }}
   555  T1: Len Content: {{ len $scss.Content }}|RelPermalink: {{ $scss.RelPermalink }}|Permalink: {{ $scss.Permalink }}|MediaType: {{ $scss.MediaType.Type }}
   556  T2: Content: {{ $scssMin.Content }}|RelPermalink: {{ $scssMin.RelPermalink }}
   557  T3: Content: {{ len $scssCustomTarget.Content }}|RelPermalink: {{ $scssCustomTarget.RelPermalink }}|MediaType: {{ $scssCustomTarget.MediaType.Type }}
   558  T4: Content: {{ len $scssCustomTargetString.Content }}|RelPermalink: {{ $scssCustomTargetString.RelPermalink }}|MediaType: {{ $scssCustomTargetString.MediaType.Type }}
   559  T5: Content: {{ $sass.Content }}|T5 RelPermalink: {{ $sass.RelPermalink }}|
   560  T6: {{ $bundle1.Permalink }}
   561  `)
   562  		}, func(b *sitesBuilder) {
   563  			b.AssertFileContent("public/index.html", `T1: Len Content: 24|RelPermalink: /scss/styles2.css|Permalink: http://example.com/scss/styles2.css|MediaType: text/css`)
   564  			b.AssertFileContent("public/index.html", `T2: Content: body{color:#333}|RelPermalink: /scss/styles2.min.css`)
   565  			b.AssertFileContent("public/index.html", `T3: Content: 24|RelPermalink: /styles/main.css|MediaType: text/css`)
   566  			b.AssertFileContent("public/index.html", `T4: Content: 24|RelPermalink: /styles/main.css|MediaType: text/css`)
   567  			b.AssertFileContent("public/index.html", `T5: Content: .content-navigation {`)
   568  			b.AssertFileContent("public/index.html", `T5 RelPermalink: /sass/styles3.css|`)
   569  			b.AssertFileContent("public/index.html", `T6: http://example.com/styles/bundle1.css`)
   570  
   571  			c.Assert(b.CheckExists("public/styles/templ.min.css"), qt.Equals, false)
   572  			b.AssertFileContent("public/styles/bundle1.css", `.home{color:blue}body{color:#333}`)
   573  		}},
   574  
   575  		{"minify", func() bool { return true }, func(b *sitesBuilder) {
   576  			b.WithConfigFile("toml", `[minify]
   577    [minify.tdewolff]
   578      [minify.tdewolff.html]
   579        keepWhitespace = false
   580  `)
   581  			b.WithTemplates("home.html", `
   582  Min CSS: {{ ( resources.Get "css/styles1.css" | minify ).Content }}
   583  Min JS: {{ ( resources.Get "js/script1.js" | resources.Minify ).Content | safeJS }}
   584  Min JSON: {{ ( resources.Get "mydata/json1.json" | resources.Minify ).Content | safeHTML }}
   585  Min XML: {{ ( resources.Get "mydata/xml1.xml" | resources.Minify ).Content | safeHTML }}
   586  Min SVG: {{ ( resources.Get "mydata/svg1.svg" | resources.Minify ).Content | safeHTML }}
   587  Min SVG again: {{ ( resources.Get "mydata/svg1.svg" | resources.Minify ).Content | safeHTML }}
   588  Min HTML: {{ ( resources.Get "mydata/html1.html" | resources.Minify ).Content | safeHTML }}
   589  
   590  
   591  `)
   592  		}, func(b *sitesBuilder) {
   593  			b.AssertFileContent("public/index.html", `Min CSS: h1{font-style:bold}`)
   594  			b.AssertFileContent("public/index.html", `Min JS: var x;x=5,document.getElementById(&#34;demo&#34;).innerHTML=x*10`)
   595  			b.AssertFileContent("public/index.html", `Min JSON: {"employees":[{"firstName":"John","lastName":"Doe"},{"firstName":"Anna","lastName":"Smith"},{"firstName":"Peter","lastName":"Jones"}]}`)
   596  			b.AssertFileContent("public/index.html", `Min XML: <hello><world>Hugo Rocks!</<world></hello>`)
   597  			b.AssertFileContent("public/index.html", `Min SVG: <svg height="100" width="100"><path d="M1e2 1e2H3e2 2e2z"/></svg>`)
   598  			b.AssertFileContent("public/index.html", `Min SVG again: <svg height="100" width="100"><path d="M1e2 1e2H3e2 2e2z"/></svg>`)
   599  			b.AssertFileContent("public/index.html", `Min HTML: <html><a href=#>Cool</a></html>`)
   600  		}},
   601  
   602  		{"concat", func() bool { return true }, func(b *sitesBuilder) {
   603  			b.WithTemplates("home.html", `
   604  {{ $a := "A" | resources.FromString "a.txt"}}
   605  {{ $b := "B" | resources.FromString "b.txt"}}
   606  {{ $c := "C" | resources.FromString "c.txt"}}
   607  {{ $textResources := .Resources.Match "*.txt" }}
   608  {{ $combined := slice $a $b $c | resources.Concat "bundle/concat.txt" }}
   609  T1: Content: {{ $combined.Content }}|RelPermalink: {{ $combined.RelPermalink }}|Permalink: {{ $combined.Permalink }}|MediaType: {{ $combined.MediaType.Type }}
   610  {{ with $textResources }}
   611  {{ $combinedText := . | resources.Concat "bundle/concattxt.txt" }}
   612  T2: Content: {{ $combinedText.Content }}|{{ $combinedText.RelPermalink }}
   613  {{ end }}
   614  {{/* https://github.com/gohugoio/hugo/issues/5269 */}}
   615  {{ $css := "body { color: blue; }" | resources.FromString "styles.css" }}
   616  {{ $minified := resources.Get "css/styles1.css" | minify }}
   617  {{ slice $css $minified | resources.Concat "bundle/mixed.css" }} 
   618  {{/* https://github.com/gohugoio/hugo/issues/5403 */}}
   619  {{ $d := "function D {} // A comment" | resources.FromString "d.js"}}
   620  {{ $e := "(function E {})" | resources.FromString "e.js"}}
   621  {{ $f := "(function F {})()" | resources.FromString "f.js"}}
   622  {{ $jsResources := .Resources.Match "*.js" }}
   623  {{ $combinedJs := slice $d $e $f | resources.Concat "bundle/concatjs.js" }}
   624  T3: Content: {{ $combinedJs.Content }}|{{ $combinedJs.RelPermalink }}
   625  `)
   626  		}, func(b *sitesBuilder) {
   627  			b.AssertFileContent("public/index.html", `T1: Content: ABC|RelPermalink: /bundle/concat.txt|Permalink: http://example.com/bundle/concat.txt|MediaType: text/plain`)
   628  			b.AssertFileContent("public/bundle/concat.txt", "ABC")
   629  
   630  			b.AssertFileContent("public/index.html", `T2: Content: t1t|t2t|`)
   631  			b.AssertFileContent("public/bundle/concattxt.txt", "t1t|t2t|")
   632  
   633  			b.AssertFileContent("public/index.html", `T3: Content: function D {} // A comment
   634  ;
   635  (function E {})
   636  ;
   637  (function F {})()|`)
   638  			b.AssertFileContent("public/bundle/concatjs.js", `function D {} // A comment
   639  ;
   640  (function E {})
   641  ;
   642  (function F {})()`)
   643  		}},
   644  
   645  		{"concat and fingerprint", func() bool { return true }, func(b *sitesBuilder) {
   646  			b.WithTemplates("home.html", `
   647  {{ $a := "A" | resources.FromString "a.txt"}}
   648  {{ $b := "B" | resources.FromString "b.txt"}}
   649  {{ $c := "C" | resources.FromString "c.txt"}}
   650  {{ $combined := slice $a $b $c | resources.Concat "bundle/concat.txt" }}
   651  {{ $fingerprinted := $combined | fingerprint }}
   652  Fingerprinted: {{ $fingerprinted.RelPermalink }}
   653  `)
   654  		}, func(b *sitesBuilder) {
   655  			b.AssertFileContent("public/index.html", "Fingerprinted: /bundle/concat.b5d4045c3f466fa91fe2cc6abe79232a1a57cdf104f7a26e716e0a1e2789df78.txt")
   656  			b.AssertFileContent("public/bundle/concat.b5d4045c3f466fa91fe2cc6abe79232a1a57cdf104f7a26e716e0a1e2789df78.txt", "ABC")
   657  		}},
   658  
   659  		{"fromstring", func() bool { return true }, func(b *sitesBuilder) {
   660  			b.WithTemplates("home.html", `
   661  {{ $r := "Hugo Rocks!" | resources.FromString "rocks/hugo.txt" }}
   662  {{ $r.Content }}|{{ $r.RelPermalink }}|{{ $r.Permalink }}|{{ $r.MediaType.Type }}
   663  `)
   664  		}, func(b *sitesBuilder) {
   665  			b.AssertFileContent("public/index.html", `Hugo Rocks!|/rocks/hugo.txt|http://example.com/rocks/hugo.txt|text/plain`)
   666  			b.AssertFileContent("public/rocks/hugo.txt", "Hugo Rocks!")
   667  		}},
   668  		{"execute-as-template", func() bool {
   669  			return true
   670  		}, func(b *sitesBuilder) {
   671  			b.WithTemplates("home.html", `
   672  {{ $var := "Hugo Page" }}
   673  {{ if .IsHome }}
   674  {{ $var = "Hugo Home" }}
   675  {{ end }}
   676  T1: {{ $var }}
   677  {{ $result := "{{ .Kind | upper }}" | resources.FromString "mytpl.txt" | resources.ExecuteAsTemplate "result.txt" . }}
   678  T2: {{ $result.Content }}|{{ $result.RelPermalink}}|{{$result.MediaType.Type }}
   679  `)
   680  		}, func(b *sitesBuilder) {
   681  			b.AssertFileContent("public/index.html", `T2: HOME|/result.txt|text/plain`, `T1: Hugo Home`)
   682  		}},
   683  		{"fingerprint", func() bool { return true }, func(b *sitesBuilder) {
   684  			b.WithTemplates("home.html", `
   685  {{ $r := "ab" | resources.FromString "rocks/hugo.txt" }}
   686  {{ $result := $r | fingerprint }}
   687  {{ $result512 := $r | fingerprint "sha512" }}
   688  {{ $resultMD5 := $r | fingerprint "md5" }}
   689  T1: {{ $result.Content }}|{{ $result.RelPermalink}}|{{$result.MediaType.Type }}|{{ $result.Data.Integrity }}|
   690  T2: {{ $result512.Content }}|{{ $result512.RelPermalink}}|{{$result512.MediaType.Type }}|{{ $result512.Data.Integrity }}|
   691  T3: {{ $resultMD5.Content }}|{{ $resultMD5.RelPermalink}}|{{$resultMD5.MediaType.Type }}|{{ $resultMD5.Data.Integrity }}|
   692  {{ $r2 := "bc" | resources.FromString "rocks/hugo2.txt" | fingerprint }}
   693  {{/* https://github.com/gohugoio/hugo/issues/5296 */}}
   694  T4: {{ $r2.Data.Integrity }}|
   695  
   696  
   697  `)
   698  		}, func(b *sitesBuilder) {
   699  			b.AssertFileContent("public/index.html", `T1: ab|/rocks/hugo.fb8e20fc2e4c3f248c60c39bd652f3c1347298bb977b8b4d5903b85055620603.txt|text/plain|sha256-&#43;44g/C5MPySMYMOb1lLzwTRymLuXe4tNWQO4UFViBgM=|`)
   700  			b.AssertFileContent("public/index.html", `T2: ab|/rocks/hugo.2d408a0717ec188158278a796c689044361dc6fdde28d6f04973b80896e1823975cdbf12eb63f9e0591328ee235d80e9b5bf1aa6a44f4617ff3caf6400eb172d.txt|text/plain|sha512-LUCKBxfsGIFYJ4p5bGiQRDYdxv3eKNbwSXO4CJbhgjl1zb8S62P54FkTKO4jXYDptb8apqRPRhf/PK9kAOsXLQ==|`)
   701  			b.AssertFileContent("public/index.html", `T3: ab|/rocks/hugo.187ef4436122d1cc2f40dc2b92f0eba0.txt|text/plain|md5-GH70Q2Ei0cwvQNwrkvDroA==|`)
   702  			b.AssertFileContent("public/index.html", `T4: sha256-Hgu9bGhroFC46wP/7txk/cnYCUf86CGrvl1tyNJSxaw=|`)
   703  		}},
   704  		// https://github.com/gohugoio/hugo/issues/5226
   705  		{"baseurl-path", func() bool { return true }, func(b *sitesBuilder) {
   706  			b.WithSimpleConfigFileAndBaseURL("https://example.com/hugo/")
   707  			b.WithTemplates("home.html", `
   708  {{ $r1 := "ab" | resources.FromString "rocks/hugo.txt" }}
   709  T1: {{ $r1.Permalink }}|{{ $r1.RelPermalink }}
   710  `)
   711  		}, func(b *sitesBuilder) {
   712  			b.AssertFileContent("public/index.html", `T1: https://example.com/hugo/rocks/hugo.txt|/hugo/rocks/hugo.txt`)
   713  		}},
   714  
   715  		// https://github.com/gohugoio/hugo/issues/4944
   716  		{"Prevent resource publish on .Content only", func() bool { return true }, func(b *sitesBuilder) {
   717  			b.WithTemplates("home.html", `
   718  {{ $cssInline := "body { color: green; }" | resources.FromString "inline.css" | minify }}
   719  {{ $cssPublish1 := "body { color: blue; }" | resources.FromString "external1.css" | minify }}
   720  {{ $cssPublish2 := "body { color: orange; }" | resources.FromString "external2.css" | minify }}
   721  
   722  Inline: {{ $cssInline.Content }}
   723  Publish 1: {{ $cssPublish1.Content }} {{ $cssPublish1.RelPermalink }}
   724  Publish 2: {{ $cssPublish2.Permalink }}
   725  `)
   726  		}, func(b *sitesBuilder) {
   727  			b.AssertFileContent("public/index.html",
   728  				`Inline: body{color:green}`,
   729  				"Publish 1: body{color:blue} /external1.min.css",
   730  				"Publish 2: http://example.com/external2.min.css",
   731  			)
   732  			b.Assert(b.CheckExists("public/external2.css"), qt.Equals, false)
   733  			b.Assert(b.CheckExists("public/external1.css"), qt.Equals, false)
   734  			b.Assert(b.CheckExists("public/external2.min.css"), qt.Equals, true)
   735  			b.Assert(b.CheckExists("public/external1.min.css"), qt.Equals, true)
   736  			b.Assert(b.CheckExists("public/inline.min.css"), qt.Equals, false)
   737  		}},
   738  
   739  		{"unmarshal", func() bool { return true }, func(b *sitesBuilder) {
   740  			b.WithTemplates("home.html", `
   741  {{ $toml := "slogan = \"Hugo Rocks!\"" | resources.FromString "slogan.toml" | transform.Unmarshal }}
   742  {{ $csv1 := "\"Hugo Rocks\",\"Hugo is Fast!\"" | resources.FromString "slogans.csv" | transform.Unmarshal }}
   743  {{ $csv2 := "a;b;c" | transform.Unmarshal (dict "delimiter" ";") }}
   744  
   745  Slogan: {{ $toml.slogan }}
   746  CSV1: {{ $csv1 }} {{ len (index $csv1 0)  }}
   747  CSV2: {{ $csv2 }}		
   748  `)
   749  		}, func(b *sitesBuilder) {
   750  			b.AssertFileContent("public/index.html",
   751  				`Slogan: Hugo Rocks!`,
   752  				`[[Hugo Rocks Hugo is Fast!]] 2`,
   753  				`CSV2: [[a b c]]`,
   754  			)
   755  		}},
   756  		{"resources.Get", func() bool { return true }, func(b *sitesBuilder) {
   757  			b.WithTemplates("home.html", `NOT FOUND: {{ if (resources.Get "this-does-not-exist") }}FAILED{{ else }}OK{{ end }}`)
   758  		}, func(b *sitesBuilder) {
   759  			b.AssertFileContent("public/index.html", "NOT FOUND: OK")
   760  		}},
   761  
   762  		{"template", func() bool { return true }, func(b *sitesBuilder) {}, func(b *sitesBuilder) {
   763  		}},
   764  	}
   765  
   766  	for _, test := range tests {
   767  		test := test
   768  		t.Run(test.name, func(t *testing.T) {
   769  			if !test.shouldRun() {
   770  				t.Skip()
   771  			}
   772  			t.Parallel()
   773  
   774  			b := newTestSitesBuilder(t).WithLogger(loggers.NewErrorLogger())
   775  			b.WithContent("_index.md", `
   776  ---
   777  title: Home
   778  ---
   779  
   780  Home.
   781  
   782  `,
   783  				"page1.md", `
   784  ---
   785  title: Hello1
   786  ---
   787  
   788  Hello1
   789  `,
   790  				"page2.md", `
   791  ---
   792  title: Hello2
   793  ---
   794  
   795  Hello2
   796  `,
   797  				"t1.txt", "t1t|",
   798  				"t2.txt", "t2t|",
   799  			)
   800  
   801  			b.WithSourceFile(filepath.Join("assets", "css", "styles1.css"), `
   802  h1 {
   803  	 font-style: bold;
   804  }
   805  `)
   806  
   807  			b.WithSourceFile(filepath.Join("assets", "js", "script1.js"), `
   808  var x;
   809  x = 5;
   810  document.getElementById("demo").innerHTML = x * 10;
   811  `)
   812  
   813  			b.WithSourceFile(filepath.Join("assets", "mydata", "json1.json"), `
   814  {
   815  "employees":[
   816      {"firstName":"John", "lastName":"Doe"}, 
   817      {"firstName":"Anna", "lastName":"Smith"},
   818      {"firstName":"Peter", "lastName":"Jones"}
   819  ]
   820  }
   821  `)
   822  
   823  			b.WithSourceFile(filepath.Join("assets", "mydata", "svg1.svg"), `
   824  <svg height="100" width="100">
   825    <path d="M 100 100 L 300 100 L 200 100 z"/>
   826  </svg> 
   827  `)
   828  
   829  			b.WithSourceFile(filepath.Join("assets", "mydata", "xml1.xml"), `
   830  <hello>
   831  <world>Hugo Rocks!</<world>
   832  </hello>
   833  `)
   834  
   835  			b.WithSourceFile(filepath.Join("assets", "mydata", "html1.html"), `
   836  <html>
   837  <a  href="#">
   838  Cool
   839  </a >
   840  </html>
   841  `)
   842  
   843  			b.WithSourceFile(filepath.Join("assets", "scss", "styles2.scss"), `
   844  $color: #333;
   845  
   846  body {
   847    color: $color;
   848  }
   849  `)
   850  
   851  			b.WithSourceFile(filepath.Join("assets", "sass", "styles3.sass"), `
   852  $color: #333;
   853  
   854  .content-navigation
   855    border-color: $color
   856  
   857  `)
   858  
   859  			test.prepare(b)
   860  			b.Build(BuildCfg{})
   861  			test.verify(b)
   862  		})
   863  	}
   864  }
   865  
   866  func TestMultiSiteResource(t *testing.T) {
   867  	t.Parallel()
   868  	c := qt.New(t)
   869  
   870  	b := newMultiSiteTestDefaultBuilder(t)
   871  
   872  	b.CreateSites().Build(BuildCfg{})
   873  
   874  	// This build is multilingual, but not multihost. There should be only one pipes.txt
   875  	b.AssertFileContent("public/fr/index.html", "French Home Page", "String Resource: /blog/text/pipes.txt")
   876  	c.Assert(b.CheckExists("public/fr/text/pipes.txt"), qt.Equals, false)
   877  	c.Assert(b.CheckExists("public/en/text/pipes.txt"), qt.Equals, false)
   878  	b.AssertFileContent("public/en/index.html", "Default Home Page", "String Resource: /blog/text/pipes.txt")
   879  	b.AssertFileContent("public/text/pipes.txt", "Hugo Pipes")
   880  }
   881  
   882  func TestResourcesMatch(t *testing.T) {
   883  	t.Parallel()
   884  
   885  	b := newTestSitesBuilder(t)
   886  
   887  	b.WithContent("page.md", "")
   888  
   889  	b.WithSourceFile(
   890  		"assets/jsons/data1.json", "json1 content",
   891  		"assets/jsons/data2.json", "json2 content",
   892  		"assets/jsons/data3.xml", "xml content",
   893  	)
   894  
   895  	b.WithTemplates("index.html", `
   896  {{ $jsons := (resources.Match "jsons/*.json") }}
   897  {{ $json := (resources.GetMatch "jsons/*.json") }}
   898  {{ printf "JSONS: %d"  (len $jsons) }}
   899  JSON: {{ $json.RelPermalink }}: {{ $json.Content }}
   900  {{ range $jsons }}
   901  {{- .RelPermalink }}: {{ .Content }}
   902  {{ end }}
   903  `)
   904  
   905  	b.Build(BuildCfg{})
   906  
   907  	b.AssertFileContent("public/index.html",
   908  		"JSON: /jsons/data1.json: json1 content",
   909  		"JSONS: 2", "/jsons/data1.json: json1 content")
   910  }
   911  
   912  func TestExecuteAsTemplateWithLanguage(t *testing.T) {
   913  	b := newMultiSiteTestDefaultBuilder(t)
   914  	indexContent := `
   915  Lang: {{ site.Language.Lang }}
   916  {{ $templ := "{{T \"hello\"}}" | resources.FromString "f1.html" }}
   917  {{ $helloResource := $templ | resources.ExecuteAsTemplate (print "f%s.html" .Lang) . }}
   918  Hello1: {{T "hello"}}
   919  Hello2: {{ $helloResource.Content }}
   920  LangURL: {{ relLangURL "foo" }}
   921  `
   922  	b.WithTemplatesAdded("index.html", indexContent)
   923  	b.WithTemplatesAdded("index.fr.html", indexContent)
   924  
   925  	b.Build(BuildCfg{})
   926  
   927  	b.AssertFileContent("public/en/index.html", `
   928  Hello1: Hello
   929  Hello2: Hello
   930  `)
   931  
   932  	b.AssertFileContent("public/fr/index.html", `
   933  Hello1: Bonjour
   934  Hello2: Bonjour
   935  `)
   936  }
   937  
   938  func TestResourceChainPostCSS(t *testing.T) {
   939  	if !htesting.IsCI() {
   940  		t.Skip("skip (relative) long running modules test when running locally")
   941  	}
   942  
   943  	wd, _ := os.Getwd()
   944  	defer func() {
   945  		os.Chdir(wd)
   946  	}()
   947  
   948  	c := qt.New(t)
   949  
   950  	packageJSON := `{
   951    "scripts": {},
   952  
   953    "devDependencies": {
   954      "postcss-cli": "7.1.0",
   955      "tailwindcss": "1.2.0"
   956    }
   957  }
   958  `
   959  
   960  	postcssConfig := `
   961  console.error("Hugo Environment:", process.env.HUGO_ENVIRONMENT );
   962  // https://github.com/gohugoio/hugo/issues/7656
   963  console.error("package.json:", process.env.HUGO_FILE_PACKAGE_JSON );
   964  console.error("PostCSS Config File:", process.env.HUGO_FILE_POSTCSS_CONFIG_JS );
   965  
   966  
   967  module.exports = {
   968    plugins: [
   969      require('tailwindcss')
   970    ]
   971  }
   972  `
   973  
   974  	tailwindCss := `
   975  @tailwind base;
   976  @tailwind components;
   977  @tailwind utilities;
   978  
   979  @import "components/all.css";
   980  
   981  h1 {
   982      @apply text-2xl font-bold;
   983  }
   984    
   985  `
   986  
   987  	workDir, clean, err := htesting.CreateTempDir(hugofs.Os, "hugo-test-postcss")
   988  	c.Assert(err, qt.IsNil)
   989  	defer clean()
   990  
   991  	var logBuf bytes.Buffer
   992  
   993  	newTestBuilder := func(v config.Provider) *sitesBuilder {
   994  		v.Set("workingDir", workDir)
   995  		v.Set("disableKinds", []string{"taxonomy", "term", "page"})
   996  		logger := loggers.NewBasicLoggerForWriter(jww.LevelInfo, &logBuf)
   997  		b := newTestSitesBuilder(t).WithLogger(logger)
   998  		// Need to use OS fs for this.
   999  		b.Fs = hugofs.NewDefault(v)
  1000  		b.WithWorkingDir(workDir)
  1001  		b.WithViper(v)
  1002  
  1003  		b.WithContent("p1.md", "")
  1004  		b.WithTemplates("index.html", `
  1005  {{ $options := dict "inlineImports" true }}
  1006  {{ $styles := resources.Get "css/styles.css" | resources.PostCSS $options }}
  1007  Styles RelPermalink: {{ $styles.RelPermalink }}
  1008  {{ $cssContent := $styles.Content }}
  1009  Styles Content: Len: {{ len $styles.Content }}|
  1010  
  1011  `)
  1012  
  1013  		return b
  1014  	}
  1015  
  1016  	b := newTestBuilder(config.New())
  1017  
  1018  	cssDir := filepath.Join(workDir, "assets", "css", "components")
  1019  	b.Assert(os.MkdirAll(cssDir, 0777), qt.IsNil)
  1020  
  1021  	b.WithSourceFile("assets/css/styles.css", tailwindCss)
  1022  	b.WithSourceFile("assets/css/components/all.css", `
  1023  @import "a.css";
  1024  @import "b.css";
  1025  `, "assets/css/components/a.css", `
  1026  class-in-a {
  1027  	color: blue;
  1028  }
  1029  `, "assets/css/components/b.css", `
  1030  @import "a.css";
  1031  
  1032  class-in-b {
  1033  	color: blue;
  1034  }
  1035  `)
  1036  
  1037  	b.WithSourceFile("package.json", packageJSON)
  1038  	b.WithSourceFile("postcss.config.js", postcssConfig)
  1039  
  1040  	b.Assert(os.Chdir(workDir), qt.IsNil)
  1041  	cmd, err := hexec.SafeCommand("npm", "install")
  1042  	_, err = cmd.CombinedOutput()
  1043  	b.Assert(err, qt.IsNil)
  1044  	b.Build(BuildCfg{})
  1045  
  1046  	// Make sure Node sees this.
  1047  	b.Assert(logBuf.String(), qt.Contains, "Hugo Environment: production")
  1048  	b.Assert(logBuf.String(), qt.Contains, filepath.FromSlash(fmt.Sprintf("PostCSS Config File: %s/postcss.config.js", workDir)))
  1049  	b.Assert(logBuf.String(), qt.Contains, filepath.FromSlash(fmt.Sprintf("package.json: %s/package.json", workDir)))
  1050  
  1051  	b.AssertFileContent("public/index.html", `
  1052  Styles RelPermalink: /css/styles.css
  1053  Styles Content: Len: 770878|
  1054  `)
  1055  
  1056  	assertCss := func(b *sitesBuilder) {
  1057  		content := b.FileContent("public/css/styles.css")
  1058  
  1059  		b.Assert(strings.Contains(content, "class-in-a"), qt.Equals, true)
  1060  		b.Assert(strings.Contains(content, "class-in-b"), qt.Equals, true)
  1061  	}
  1062  
  1063  	assertCss(b)
  1064  
  1065  	build := func(s string, shouldFail bool) error {
  1066  		b.Assert(os.RemoveAll(filepath.Join(workDir, "public")), qt.IsNil)
  1067  
  1068  		v := config.New()
  1069  		v.Set("build", map[string]interface{}{
  1070  			"useResourceCacheWhen": s,
  1071  		})
  1072  
  1073  		b = newTestBuilder(v)
  1074  
  1075  		b.Assert(os.RemoveAll(filepath.Join(workDir, "public")), qt.IsNil)
  1076  
  1077  		err := b.BuildE(BuildCfg{})
  1078  		if shouldFail {
  1079  			b.Assert(err, qt.Not(qt.IsNil))
  1080  		} else {
  1081  			b.Assert(err, qt.IsNil)
  1082  			assertCss(b)
  1083  		}
  1084  
  1085  		return err
  1086  	}
  1087  
  1088  	build("always", false)
  1089  	build("fallback", false)
  1090  
  1091  	// Introduce a syntax error in an import
  1092  	b.WithSourceFile("assets/css/components/b.css", `@import "a.css";
  1093  
  1094  class-in-b {
  1095  	@apply asdf;
  1096  }
  1097  `)
  1098  
  1099  	err = build("newer", true)
  1100  
  1101  	err = herrors.UnwrapErrorWithFileContext(err)
  1102  	fe, ok := err.(*herrors.ErrorWithFileContext)
  1103  	b.Assert(ok, qt.Equals, true)
  1104  	b.Assert(fe.Position().LineNumber, qt.Equals, 4)
  1105  	b.Assert(fe.Error(), qt.Contains, filepath.Join(workDir, "assets/css/components/b.css:4:1"))
  1106  
  1107  	// Remove PostCSS
  1108  	b.Assert(os.RemoveAll(filepath.Join(workDir, "node_modules")), qt.IsNil)
  1109  
  1110  	build("always", false)
  1111  	build("fallback", false)
  1112  	build("never", true)
  1113  
  1114  	// Remove cache
  1115  	b.Assert(os.RemoveAll(filepath.Join(workDir, "resources")), qt.IsNil)
  1116  
  1117  	build("always", true)
  1118  	build("fallback", true)
  1119  	build("never", true)
  1120  }
  1121  
  1122  func TestResourceMinifyDisabled(t *testing.T) {
  1123  	t.Parallel()
  1124  
  1125  	b := newTestSitesBuilder(t).WithConfigFile("toml", `
  1126  baseURL = "https://example.org"
  1127  
  1128  [minify]
  1129  disableXML=true
  1130  
  1131  
  1132  `)
  1133  
  1134  	b.WithContent("page.md", "")
  1135  
  1136  	b.WithSourceFile(
  1137  		"assets/xml/data.xml", "<root>   <foo> asdfasdf </foo> </root>",
  1138  	)
  1139  
  1140  	b.WithTemplates("index.html", `
  1141  {{ $xml := resources.Get "xml/data.xml" | minify | fingerprint }}
  1142  XML: {{ $xml.Content | safeHTML }}|{{ $xml.RelPermalink }}
  1143  `)
  1144  
  1145  	b.Build(BuildCfg{})
  1146  
  1147  	b.AssertFileContent("public/index.html", `
  1148  XML: <root>   <foo> asdfasdf </foo> </root>|/xml/data.min.3be4fddd19aaebb18c48dd6645215b822df74701957d6d36e59f203f9c30fd9f.xml
  1149  `)
  1150  }