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+8= /styles.min.a1df58687c3c9cc38bf26532f7b4b2f2c2b0315dcde212376959995c04f11fef.css 431 CSS integrity Data last: /styles2.min.1cfc52986836405d37f9998a63fd6dd8608e8c410e5e3db1daaa30f78bc273ba.css sha256-HPxSmGg2QF03+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("demo").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-+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 }