github.com/gohugoio/hugo@v0.88.1/hugolib/hugo_modules_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 "math/rand" 19 "os" 20 "path/filepath" 21 "strings" 22 "testing" 23 "time" 24 25 "github.com/gohugoio/hugo/config" 26 "github.com/gohugoio/hugo/modules/npm" 27 28 "github.com/gohugoio/hugo/common/loggers" 29 30 "github.com/spf13/afero" 31 32 "github.com/gohugoio/hugo/hugofs/files" 33 34 "github.com/gohugoio/hugo/common/hugo" 35 36 "github.com/gohugoio/hugo/htesting" 37 "github.com/gohugoio/hugo/hugofs" 38 39 qt "github.com/frankban/quicktest" 40 "github.com/gohugoio/testmodBuilder/mods" 41 ) 42 43 func TestHugoModulesVariants(t *testing.T) { 44 if !htesting.IsCI() { 45 t.Skip("skip (relative) long running modules test when running locally") 46 } 47 48 tomlConfig := ` 49 baseURL="https://example.org" 50 workingDir = %q 51 52 [module] 53 [[module.imports]] 54 path="github.com/gohugoio/hugoTestModule2" 55 %s 56 ` 57 58 createConfig := func(workingDir, moduleOpts string) string { 59 return fmt.Sprintf(tomlConfig, workingDir, moduleOpts) 60 } 61 62 newTestBuilder := func(t testing.TB, moduleOpts string) (*sitesBuilder, func()) { 63 b := newTestSitesBuilder(t) 64 tempDir, clean, err := htesting.CreateTempDir(hugofs.Os, "hugo-modules-variants") 65 b.Assert(err, qt.IsNil) 66 workingDir := filepath.Join(tempDir, "myhugosite") 67 b.Assert(os.MkdirAll(workingDir, 0777), qt.IsNil) 68 b.Fs = hugofs.NewDefault(config.New()) 69 b.WithWorkingDir(workingDir).WithConfigFile("toml", createConfig(workingDir, moduleOpts)) 70 b.WithTemplates( 71 "index.html", ` 72 Param from module: {{ site.Params.Hugo }}| 73 {{ $js := resources.Get "jslibs/alpinejs/alpine.js" }} 74 JS imported in module: {{ with $js }}{{ .RelPermalink }}{{ end }}| 75 `, 76 "_default/single.html", `{{ .Content }}`) 77 b.WithContent("p1.md", `--- 78 title: "Page" 79 --- 80 81 [A link](https://bep.is) 82 83 `) 84 b.WithSourceFile("go.mod", ` 85 module github.com/gohugoio/tests/testHugoModules 86 87 88 `) 89 90 b.WithSourceFile("go.sum", ` 91 github.com/gohugoio/hugoTestModule2 v0.0.0-20200131160637-9657d7697877 h1:WLM2bQCKIWo04T6NsIWsX/Vtirhf0TnpY66xyqGlgVY= 92 github.com/gohugoio/hugoTestModule2 v0.0.0-20200131160637-9657d7697877/go.mod h1:CBFZS3khIAXKxReMwq0le8sEl/D8hcXmixlOHVv+Gd0= 93 `) 94 95 return b, clean 96 } 97 98 t.Run("Target in subfolder", func(t *testing.T) { 99 b, clean := newTestBuilder(t, "ignoreImports=true") 100 defer clean() 101 102 b.Build(BuildCfg{}) 103 104 b.AssertFileContent("public/p1/index.html", `<p>Page|https://bep.is|Title: |Text: A link|END</p>`) 105 }) 106 107 t.Run("Ignore config", func(t *testing.T) { 108 b, clean := newTestBuilder(t, "ignoreConfig=true") 109 defer clean() 110 111 b.Build(BuildCfg{}) 112 113 b.AssertFileContent("public/index.html", ` 114 Param from module: | 115 JS imported in module: | 116 `) 117 }) 118 119 t.Run("Ignore imports", func(t *testing.T) { 120 b, clean := newTestBuilder(t, "ignoreImports=true") 121 defer clean() 122 123 b.Build(BuildCfg{}) 124 125 b.AssertFileContent("public/index.html", ` 126 Param from module: Rocks| 127 JS imported in module: | 128 `) 129 }) 130 131 t.Run("Create package.json", func(t *testing.T) { 132 b, clean := newTestBuilder(t, "") 133 defer clean() 134 135 b.WithSourceFile("package.json", `{ 136 "name": "mypack", 137 "version": "1.2.3", 138 "scripts": { 139 "client": "wait-on http://localhost:1313 && open http://localhost:1313", 140 "start": "run-p client server", 141 "test": "echo 'hoge' > hoge" 142 }, 143 "dependencies": { 144 "nonon": "error" 145 } 146 }`) 147 148 b.WithSourceFile("package.hugo.json", `{ 149 "name": "mypack", 150 "version": "1.2.3", 151 "scripts": { 152 "client": "wait-on http://localhost:1313 && open http://localhost:1313", 153 "start": "run-p client server", 154 "test": "echo 'hoge' > hoge" 155 }, 156 "dependencies": { 157 "foo": "1.2.3" 158 }, 159 "devDependencies": { 160 "postcss-cli": "7.8.0", 161 "tailwindcss": "1.8.0" 162 163 } 164 }`) 165 166 b.Build(BuildCfg{}) 167 b.Assert(npm.Pack(b.H.BaseFs.SourceFs, b.H.BaseFs.Assets.Dirs), qt.IsNil) 168 169 b.AssertFileContentFn("package.json", func(s string) bool { 170 return s == `{ 171 "comments": { 172 "dependencies": { 173 "foo": "project", 174 "react-dom": "github.com/gohugoio/hugoTestModule2" 175 }, 176 "devDependencies": { 177 "@babel/cli": "github.com/gohugoio/hugoTestModule2", 178 "@babel/core": "github.com/gohugoio/hugoTestModule2", 179 "@babel/preset-env": "github.com/gohugoio/hugoTestModule2", 180 "postcss-cli": "project", 181 "tailwindcss": "project" 182 } 183 }, 184 "dependencies": { 185 "foo": "1.2.3", 186 "react-dom": "^16.13.1" 187 }, 188 "devDependencies": { 189 "@babel/cli": "7.8.4", 190 "@babel/core": "7.9.0", 191 "@babel/preset-env": "7.9.5", 192 "postcss-cli": "7.8.0", 193 "tailwindcss": "1.8.0" 194 }, 195 "name": "mypack", 196 "scripts": { 197 "client": "wait-on http://localhost:1313 && open http://localhost:1313", 198 "start": "run-p client server", 199 "test": "echo 'hoge' > hoge" 200 }, 201 "version": "1.2.3" 202 } 203 ` 204 }) 205 }) 206 207 t.Run("Create package.json, no default", func(t *testing.T) { 208 b, clean := newTestBuilder(t, "") 209 defer clean() 210 211 const origPackageJSON = `{ 212 "name": "mypack", 213 "version": "1.2.3", 214 "scripts": { 215 "client": "wait-on http://localhost:1313 && open http://localhost:1313", 216 "start": "run-p client server", 217 "test": "echo 'hoge' > hoge" 218 }, 219 "dependencies": { 220 "moo": "1.2.3" 221 } 222 }` 223 224 b.WithSourceFile("package.json", origPackageJSON) 225 226 b.Build(BuildCfg{}) 227 b.Assert(npm.Pack(b.H.BaseFs.SourceFs, b.H.BaseFs.Assets.Dirs), qt.IsNil) 228 229 b.AssertFileContentFn("package.json", func(s string) bool { 230 return s == `{ 231 "comments": { 232 "dependencies": { 233 "moo": "project", 234 "react-dom": "github.com/gohugoio/hugoTestModule2" 235 }, 236 "devDependencies": { 237 "@babel/cli": "github.com/gohugoio/hugoTestModule2", 238 "@babel/core": "github.com/gohugoio/hugoTestModule2", 239 "@babel/preset-env": "github.com/gohugoio/hugoTestModule2", 240 "postcss-cli": "github.com/gohugoio/hugoTestModule2", 241 "tailwindcss": "github.com/gohugoio/hugoTestModule2" 242 } 243 }, 244 "dependencies": { 245 "moo": "1.2.3", 246 "react-dom": "^16.13.1" 247 }, 248 "devDependencies": { 249 "@babel/cli": "7.8.4", 250 "@babel/core": "7.9.0", 251 "@babel/preset-env": "7.9.5", 252 "postcss-cli": "7.1.0", 253 "tailwindcss": "1.2.0" 254 }, 255 "name": "mypack", 256 "scripts": { 257 "client": "wait-on http://localhost:1313 && open http://localhost:1313", 258 "start": "run-p client server", 259 "test": "echo 'hoge' > hoge" 260 }, 261 "version": "1.2.3" 262 } 263 ` 264 }) 265 266 // https://github.com/gohugoio/hugo/issues/7690 267 b.AssertFileContent("package.hugo.json", origPackageJSON) 268 }) 269 270 t.Run("Create package.json, no default, no package.json", func(t *testing.T) { 271 b, clean := newTestBuilder(t, "") 272 defer clean() 273 274 b.Build(BuildCfg{}) 275 b.Assert(npm.Pack(b.H.BaseFs.SourceFs, b.H.BaseFs.Assets.Dirs), qt.IsNil) 276 277 b.AssertFileContentFn("package.json", func(s string) bool { 278 return s == `{ 279 "comments": { 280 "dependencies": { 281 "react-dom": "github.com/gohugoio/hugoTestModule2" 282 }, 283 "devDependencies": { 284 "@babel/cli": "github.com/gohugoio/hugoTestModule2", 285 "@babel/core": "github.com/gohugoio/hugoTestModule2", 286 "@babel/preset-env": "github.com/gohugoio/hugoTestModule2", 287 "postcss-cli": "github.com/gohugoio/hugoTestModule2", 288 "tailwindcss": "github.com/gohugoio/hugoTestModule2" 289 } 290 }, 291 "dependencies": { 292 "react-dom": "^16.13.1" 293 }, 294 "devDependencies": { 295 "@babel/cli": "7.8.4", 296 "@babel/core": "7.9.0", 297 "@babel/preset-env": "7.9.5", 298 "postcss-cli": "7.1.0", 299 "tailwindcss": "1.2.0" 300 }, 301 "name": "myhugosite", 302 "version": "0.1.0" 303 } 304 ` 305 }) 306 }) 307 } 308 309 // TODO(bep) this fails when testmodBuilder is also building ... 310 func TestHugoModulesMatrix(t *testing.T) { 311 if !htesting.IsCI() { 312 t.Skip("skip (relative) long running modules test when running locally") 313 } 314 t.Parallel() 315 316 if !htesting.IsCI() || hugo.GoMinorVersion() < 12 { 317 // https://github.com/golang/go/issues/26794 318 // There were some concurrent issues with Go modules in < Go 12. 319 t.Skip("skip this on local host and for Go <= 1.11 due to a bug in Go's stdlib") 320 } 321 322 if testing.Short() { 323 t.Skip() 324 } 325 326 rnd := rand.New(rand.NewSource(time.Now().UnixNano())) 327 gooss := []string{"linux", "darwin", "windows"} 328 goos := gooss[rnd.Intn(len(gooss))] 329 ignoreVendor := rnd.Intn(2) == 0 330 testmods := mods.CreateModules(goos).Collect() 331 rnd.Shuffle(len(testmods), func(i, j int) { testmods[i], testmods[j] = testmods[j], testmods[i] }) 332 333 for _, m := range testmods[:2] { 334 c := qt.New(t) 335 336 v := config.New() 337 338 workingDir, clean, err := htesting.CreateTempDir(hugofs.Os, "hugo-modules-test") 339 c.Assert(err, qt.IsNil) 340 defer clean() 341 342 configTemplate := ` 343 baseURL = "https://example.com" 344 title = "My Modular Site" 345 workingDir = %q 346 theme = %q 347 ignoreVendorPaths = %q 348 349 ` 350 351 ignoreVendorPaths := "" 352 if ignoreVendor { 353 ignoreVendorPaths = "github.com/**" 354 } 355 config := fmt.Sprintf(configTemplate, workingDir, m.Path(), ignoreVendorPaths) 356 357 b := newTestSitesBuilder(t) 358 359 // Need to use OS fs for this. 360 b.Fs = hugofs.NewDefault(v) 361 362 b.WithWorkingDir(workingDir).WithConfigFile("toml", config) 363 b.WithContent("page.md", ` 364 --- 365 title: "Foo" 366 --- 367 `) 368 b.WithTemplates("home.html", ` 369 370 {{ $mod := .Site.Data.modinfo.module }} 371 Mod Name: {{ $mod.name }} 372 Mod Version: {{ $mod.version }} 373 ---- 374 {{ range $k, $v := .Site.Data.modinfo }} 375 - {{ $k }}: {{ range $kk, $vv := $v }}{{ $kk }}: {{ $vv }}|{{ end -}} 376 {{ end }} 377 378 379 `) 380 b.WithSourceFile("go.mod", ` 381 module github.com/gohugoio/tests/testHugoModules 382 383 384 `) 385 386 b.Build(BuildCfg{}) 387 388 // Verify that go.mod is autopopulated with all the modules in config.toml. 389 b.AssertFileContent("go.mod", m.Path()) 390 391 b.AssertFileContent("public/index.html", 392 "Mod Name: "+m.Name(), 393 "Mod Version: v1.4.0") 394 395 b.AssertFileContent("public/index.html", createChildModMatchers(m, ignoreVendor, m.Vendor)...) 396 397 } 398 } 399 400 func createChildModMatchers(m *mods.Md, ignoreVendor, vendored bool) []string { 401 // Child dependencies are one behind. 402 expectMinorVersion := 3 403 404 if !ignoreVendor && vendored { 405 // Vendored modules are stuck at v1.1.0. 406 expectMinorVersion = 1 407 } 408 409 expectVersion := fmt.Sprintf("v1.%d.0", expectMinorVersion) 410 411 var matchers []string 412 413 for _, mm := range m.Children { 414 matchers = append( 415 matchers, 416 fmt.Sprintf("%s: name: %s|version: %s", mm.Name(), mm.Name(), expectVersion)) 417 matchers = append(matchers, createChildModMatchers(mm, ignoreVendor, vendored || mm.Vendor)...) 418 } 419 return matchers 420 } 421 422 func TestModulesWithContent(t *testing.T) { 423 t.Parallel() 424 425 b := newTestSitesBuilder(t).WithWorkingDir("/site").WithConfigFile("toml", ` 426 baseURL="https://example.org" 427 428 workingDir="/site" 429 430 defaultContentLanguage = "en" 431 432 [module] 433 [[module.imports]] 434 path="a" 435 [[module.imports.mounts]] 436 source="myacontent" 437 target="content/blog" 438 lang="en" 439 [[module.imports]] 440 path="b" 441 [[module.imports.mounts]] 442 source="mybcontent" 443 target="content/blog" 444 lang="nn" 445 [[module.imports]] 446 path="c" 447 [[module.imports]] 448 path="d" 449 450 [languages] 451 452 [languages.en] 453 title = "Title in English" 454 languageName = "English" 455 weight = 1 456 [languages.nn] 457 languageName = "Nynorsk" 458 weight = 2 459 title = "Tittel på nynorsk" 460 [languages.nb] 461 languageName = "Bokmål" 462 weight = 3 463 title = "Tittel på bokmål" 464 [languages.fr] 465 languageName = "French" 466 weight = 4 467 title = "French Title" 468 469 470 `) 471 472 b.WithTemplatesAdded("index.html", ` 473 {{ range .Site.RegularPages }} 474 |{{ .Title }}|{{ .RelPermalink }}|{{ .Plain }} 475 {{ end }} 476 {{ $data := .Site.Data }} 477 Data Common: {{ $data.common.value }} 478 Data C: {{ $data.c.value }} 479 Data D: {{ $data.d.value }} 480 All Data: {{ $data }} 481 482 i18n hello1: {{ i18n "hello1" . }} 483 i18n theme: {{ i18n "theme" . }} 484 i18n theme2: {{ i18n "theme2" . }} 485 `) 486 487 content := func(id string) string { 488 return fmt.Sprintf(`--- 489 title: Title %s 490 --- 491 Content %s 492 493 `, id, id) 494 } 495 496 i18nContent := func(id, value string) string { 497 return fmt.Sprintf(` 498 [%s] 499 other = %q 500 `, id, value) 501 } 502 503 // Content files 504 b.WithSourceFile("themes/a/myacontent/page.md", content("theme-a-en")) 505 b.WithSourceFile("themes/b/mybcontent/page.md", content("theme-b-nn")) 506 b.WithSourceFile("themes/c/content/blog/c.md", content("theme-c-nn")) 507 508 // Data files 509 b.WithSourceFile("data/common.toml", `value="Project"`) 510 b.WithSourceFile("themes/c/data/common.toml", `value="Theme C"`) 511 b.WithSourceFile("themes/c/data/c.toml", `value="Hugo Rocks!"`) 512 b.WithSourceFile("themes/d/data/c.toml", `value="Hugo Rodcks!"`) 513 b.WithSourceFile("themes/d/data/d.toml", `value="Hugo Rodks!"`) 514 515 // i18n files 516 b.WithSourceFile("i18n/en.toml", i18nContent("hello1", "Project")) 517 b.WithSourceFile("themes/c/i18n/en.toml", ` 518 [hello1] 519 other="Theme C Hello" 520 [theme] 521 other="Theme C" 522 `) 523 b.WithSourceFile("themes/d/i18n/en.toml", i18nContent("theme", "Theme D")) 524 b.WithSourceFile("themes/d/i18n/en.toml", i18nContent("theme2", "Theme2 D")) 525 526 // Static files 527 b.WithSourceFile("themes/c/static/hello.txt", `Hugo Rocks!"`) 528 529 b.Build(BuildCfg{}) 530 531 b.AssertFileContent("public/index.html", "|Title theme-a-en|/blog/page/|Content theme-a-en") 532 b.AssertFileContent("public/nn/index.html", "|Title theme-b-nn|/nn/blog/page/|Content theme-b-nn") 533 534 // Data 535 b.AssertFileContent("public/index.html", 536 "Data Common: Project", 537 "Data C: Hugo Rocks!", 538 "Data D: Hugo Rodks!", 539 ) 540 541 // i18n 542 b.AssertFileContent("public/index.html", 543 "i18n hello1: Project", 544 "i18n theme: Theme C", 545 "i18n theme2: Theme2 D", 546 ) 547 } 548 549 func TestModulesIgnoreConfig(t *testing.T) { 550 b := newTestSitesBuilder(t).WithWorkingDir("/site").WithConfigFile("toml", ` 551 baseURL="https://example.org" 552 553 workingDir="/site" 554 555 [module] 556 [[module.imports]] 557 path="a" 558 ignoreConfig=true 559 560 `) 561 562 b.WithSourceFile("themes/a/config.toml", ` 563 [params] 564 a = "Should Be Ignored!" 565 `) 566 567 b.WithTemplatesAdded("index.html", `Params: {{ .Site.Params }}`) 568 569 b.Build(BuildCfg{}) 570 571 b.AssertFileContentFn("public/index.html", func(s string) bool { 572 return !strings.Contains(s, "Ignored") 573 }) 574 } 575 576 func TestModulesDisabled(t *testing.T) { 577 b := newTestSitesBuilder(t).WithWorkingDir("/site").WithConfigFile("toml", ` 578 baseURL="https://example.org" 579 580 workingDir="/site" 581 582 [module] 583 [[module.imports]] 584 path="a" 585 [[module.imports]] 586 path="b" 587 disable=true 588 589 590 `) 591 592 b.WithSourceFile("themes/a/config.toml", ` 593 [params] 594 a = "A param" 595 `) 596 597 b.WithSourceFile("themes/b/config.toml", ` 598 [params] 599 b = "B param" 600 `) 601 602 b.WithTemplatesAdded("index.html", `Params: {{ .Site.Params }}`) 603 604 b.Build(BuildCfg{}) 605 606 b.AssertFileContentFn("public/index.html", func(s string) bool { 607 return strings.Contains(s, "A param") && !strings.Contains(s, "B param") 608 }) 609 } 610 611 func TestModulesIncompatible(t *testing.T) { 612 t.Parallel() 613 614 b := newTestSitesBuilder(t).WithWorkingDir("/site").WithConfigFile("toml", ` 615 baseURL="https://example.org" 616 617 workingDir="/site" 618 619 [module] 620 [[module.imports]] 621 path="ok" 622 [[module.imports]] 623 path="incompat1" 624 [[module.imports]] 625 path="incompat2" 626 [[module.imports]] 627 path="incompat3" 628 629 `) 630 631 b.WithSourceFile("themes/ok/data/ok.toml", `title = "OK"`) 632 633 b.WithSourceFile("themes/incompat1/config.toml", ` 634 635 [module] 636 [module.hugoVersion] 637 min = "0.33.2" 638 max = "0.45.0" 639 640 `) 641 642 // Old setup. 643 b.WithSourceFile("themes/incompat2/theme.toml", ` 644 min_version = "5.0.0" 645 646 `) 647 648 // Issue 6162 649 b.WithSourceFile("themes/incompat3/theme.toml", ` 650 min_version = 0.55.0 651 652 `) 653 654 logger := loggers.NewWarningLogger() 655 b.WithLogger(logger) 656 657 b.Build(BuildCfg{}) 658 659 c := qt.New(t) 660 661 c.Assert(logger.LogCounters().WarnCounter.Count(), qt.Equals, uint64(3)) 662 } 663 664 func TestModulesSymlinks(t *testing.T) { 665 skipSymlink(t) 666 667 wd, _ := os.Getwd() 668 defer func() { 669 os.Chdir(wd) 670 }() 671 672 c := qt.New(t) 673 // We need to use the OS fs for this. 674 cfg := config.New() 675 fs := hugofs.NewFrom(hugofs.Os, cfg) 676 677 workDir, clean, err := htesting.CreateTempDir(hugofs.Os, "hugo-mod-sym") 678 c.Assert(err, qt.IsNil) 679 680 defer clean() 681 682 const homeTemplate = ` 683 Data: {{ .Site.Data }} 684 ` 685 686 createDirsAndFiles := func(baseDir string) { 687 for _, dir := range files.ComponentFolders { 688 realDir := filepath.Join(baseDir, dir, "real") 689 c.Assert(os.MkdirAll(realDir, 0777), qt.IsNil) 690 c.Assert(afero.WriteFile(fs.Source, filepath.Join(realDir, "data.toml"), []byte("[hello]\nother = \"hello\""), 0777), qt.IsNil) 691 } 692 693 c.Assert(afero.WriteFile(fs.Source, filepath.Join(baseDir, "layouts", "index.html"), []byte(homeTemplate), 0777), qt.IsNil) 694 } 695 696 // Create project dirs and files. 697 createDirsAndFiles(workDir) 698 // Create one module inside the default themes folder. 699 themeDir := filepath.Join(workDir, "themes", "mymod") 700 createDirsAndFiles(themeDir) 701 702 createSymlinks := func(baseDir, id string) { 703 for _, dir := range files.ComponentFolders { 704 c.Assert(os.Chdir(filepath.Join(baseDir, dir)), qt.IsNil) 705 c.Assert(os.Symlink("real", fmt.Sprintf("realsym%s", id)), qt.IsNil) 706 c.Assert(os.Chdir(filepath.Join(baseDir, dir, "real")), qt.IsNil) 707 c.Assert(os.Symlink("data.toml", fmt.Sprintf(filepath.FromSlash("datasym%s.toml"), id)), qt.IsNil) 708 } 709 } 710 711 createSymlinks(workDir, "project") 712 createSymlinks(themeDir, "mod") 713 714 config := ` 715 baseURL = "https://example.com" 716 theme="mymod" 717 defaultContentLanguage="nn" 718 defaultContentLanguageInSubDir=true 719 720 [languages] 721 [languages.nn] 722 weight = 1 723 [languages.en] 724 weight = 2 725 726 727 ` 728 729 b := newTestSitesBuilder(t).WithNothingAdded().WithWorkingDir(workDir) 730 b.WithLogger(loggers.NewErrorLogger()) 731 b.Fs = fs 732 733 b.WithConfigFile("toml", config) 734 c.Assert(os.Chdir(workDir), qt.IsNil) 735 736 b.Build(BuildCfg{}) 737 738 b.AssertFileContentFn(filepath.Join("public", "en", "index.html"), func(s string) bool { 739 // Symbolic links only followed in project. There should be WARNING logs. 740 return !strings.Contains(s, "symmod") && strings.Contains(s, "symproject") 741 }) 742 743 bfs := b.H.BaseFs 744 745 for i, componentFs := range []afero.Fs{ 746 bfs.Static[""].Fs, 747 bfs.Archetypes.Fs, 748 bfs.Content.Fs, 749 bfs.Data.Fs, 750 bfs.Assets.Fs, 751 bfs.I18n.Fs, 752 } { 753 754 if i != 0 { 755 continue 756 } 757 758 for j, id := range []string{"mod", "project"} { 759 760 statCheck := func(fs afero.Fs, filename string, isDir bool) { 761 shouldFail := j == 0 762 if !shouldFail && i == 0 { 763 // Static dirs only supports symlinks for files 764 shouldFail = isDir 765 } 766 767 _, err := fs.Stat(filepath.FromSlash(filename)) 768 if err != nil { 769 if i > 0 && strings.HasSuffix(filename, "toml") && strings.Contains(err.Error(), "files not supported") { 770 // OK 771 return 772 } 773 } 774 775 if shouldFail { 776 c.Assert(err, qt.Not(qt.IsNil)) 777 c.Assert(err, qt.Equals, hugofs.ErrPermissionSymlink) 778 } else { 779 c.Assert(err, qt.IsNil) 780 } 781 } 782 783 statCheck(componentFs, fmt.Sprintf("realsym%s", id), true) 784 statCheck(componentFs, fmt.Sprintf("real/datasym%s.toml", id), false) 785 786 } 787 } 788 } 789 790 func TestMountsProject(t *testing.T) { 791 t.Parallel() 792 793 config := ` 794 795 baseURL="https://example.org" 796 797 [module] 798 [[module.mounts]] 799 source="mycontent" 800 target="content" 801 802 ` 803 b := newTestSitesBuilder(t). 804 WithConfigFile("toml", config). 805 WithSourceFile(filepath.Join("mycontent", "mypage.md"), ` 806 --- 807 title: "My Page" 808 --- 809 810 `) 811 812 b.Build(BuildCfg{}) 813 814 // helpers.PrintFs(b.H.Fs.Source, "public", os.Stdout) 815 816 b.AssertFileContent("public/mypage/index.html", "Permalink: https://example.org/mypage/") 817 } 818 819 // https://github.com/gohugoio/hugo/issues/6684 820 func TestMountsContentFile(t *testing.T) { 821 t.Parallel() 822 c := qt.New(t) 823 workingDir, clean, err := htesting.CreateTempDir(hugofs.Os, "hugo-modules-content-file") 824 c.Assert(err, qt.IsNil) 825 defer clean() 826 827 configTemplate := ` 828 baseURL = "https://example.com" 829 title = "My Modular Site" 830 workingDir = %q 831 832 [module] 833 [[module.mounts]] 834 source = "README.md" 835 target = "content/_index.md" 836 [[module.mounts]] 837 source = "mycontent" 838 target = "content/blog" 839 840 ` 841 842 tomlConfig := fmt.Sprintf(configTemplate, workingDir) 843 844 b := newTestSitesBuilder(t).Running() 845 846 b.Fs = hugofs.NewDefault(config.New()) 847 848 b.WithWorkingDir(workingDir).WithConfigFile("toml", tomlConfig) 849 b.WithTemplatesAdded("index.html", ` 850 {{ .Title }} 851 {{ .Content }} 852 853 {{ $readme := .Site.GetPage "/README.md" }} 854 {{ with $readme }}README: {{ .Title }}|Filename: {{ path.Join .File.Filename }}|Path: {{ path.Join .File.Path }}|FilePath: {{ path.Join .File.FileInfo.Meta.PathFile }}|{{ end }} 855 856 857 {{ $mypage := .Site.GetPage "/blog/mypage.md" }} 858 {{ with $mypage }}MYPAGE: {{ .Title }}|Path: {{ path.Join .File.Path }}|FilePath: {{ path.Join .File.FileInfo.Meta.PathFile }}|{{ end }} 859 {{ $mybundle := .Site.GetPage "/blog/mybundle" }} 860 {{ with $mybundle }}MYBUNDLE: {{ .Title }}|Path: {{ path.Join .File.Path }}|FilePath: {{ path.Join .File.FileInfo.Meta.PathFile }}|{{ end }} 861 862 863 `, "_default/_markup/render-link.html", ` 864 {{ $link := .Destination }} 865 {{ $isRemote := strings.HasPrefix $link "http" }} 866 {{- if not $isRemote -}} 867 {{ $url := urls.Parse .Destination }} 868 {{ $fragment := "" }} 869 {{- with $url.Fragment }}{{ $fragment = printf "#%s" . }}{{ end -}} 870 {{- with .Page.GetPage $url.Path }}{{ $link = printf "%s%s" .Permalink $fragment }}{{ end }}{{ end -}} 871 <a href="{{ $link | safeURL }}"{{ with .Title}} title="{{ . }}"{{ end }}{{ if $isRemote }} target="_blank"{{ end }}>{{ .Text | safeHTML }}</a> 872 `) 873 874 os.Mkdir(filepath.Join(workingDir, "mycontent"), 0777) 875 os.Mkdir(filepath.Join(workingDir, "mycontent", "mybundle"), 0777) 876 877 b.WithSourceFile("README.md", `--- 878 title: "Readme Title" 879 --- 880 881 Readme Content. 882 `, 883 filepath.Join("mycontent", "mypage.md"), ` 884 --- 885 title: "My Page" 886 --- 887 888 889 * [Relative Link From Page](mybundle) 890 * [Relative Link From Page, filename](mybundle/index.md) 891 * [Link using original path](/mycontent/mybundle/index.md) 892 893 894 `, filepath.Join("mycontent", "mybundle", "index.md"), ` 895 --- 896 title: "My Bundle" 897 --- 898 899 * [Dot Relative Link From Bundle](../mypage.md) 900 * [Link using original path](/mycontent/mypage.md) 901 * [Link to Home](/) 902 * [Link to Home, README.md](/README.md) 903 * [Link to Home, _index.md](/_index.md) 904 905 `) 906 907 b.Build(BuildCfg{}) 908 909 b.AssertFileContent("public/index.html", ` 910 README: Readme Title 911 /README.md|Path: _index.md|FilePath: README.md 912 Readme Content. 913 MYPAGE: My Page|Path: blog/mypage.md|FilePath: mycontent/mypage.md| 914 MYBUNDLE: My Bundle|Path: blog/mybundle/index.md|FilePath: mycontent/mybundle/index.md| 915 `) 916 b.AssertFileContent("public/blog/mypage/index.html", ` 917 <a href="https://example.com/blog/mybundle/">Relative Link From Page</a> 918 <a href="https://example.com/blog/mybundle/">Relative Link From Page, filename</a> 919 <a href="https://example.com/blog/mybundle/">Link using original path</a> 920 921 `) 922 b.AssertFileContent("public/blog/mybundle/index.html", ` 923 <a href="https://example.com/blog/mypage/">Dot Relative Link From Bundle</a> 924 <a href="https://example.com/blog/mypage/">Link using original path</a> 925 <a href="https://example.com/">Link to Home</a> 926 <a href="https://example.com/">Link to Home, README.md</a> 927 <a href="https://example.com/">Link to Home, _index.md</a> 928 `) 929 930 b.EditFiles("README.md", `--- 931 title: "Readme Edit" 932 --- 933 `) 934 935 b.Build(BuildCfg{}) 936 937 b.AssertFileContent("public/index.html", ` 938 Readme Edit 939 `) 940 } 941 942 func TestMountsPaths(t *testing.T) { 943 c := qt.New(t) 944 945 type test struct { 946 b *sitesBuilder 947 clean func() 948 workingDir string 949 } 950 951 prepare := func(c *qt.C, mounts string) test { 952 workingDir, clean, err := htesting.CreateTempDir(hugofs.Os, "hugo-mounts-paths") 953 c.Assert(err, qt.IsNil) 954 955 configTemplate := ` 956 baseURL = "https://example.com" 957 title = "My Modular Site" 958 workingDir = %q 959 960 %s 961 962 ` 963 tomlConfig := fmt.Sprintf(configTemplate, workingDir, mounts) 964 tomlConfig = strings.Replace(tomlConfig, "WORKING_DIR", workingDir, -1) 965 966 b := newTestSitesBuilder(c).Running() 967 968 b.Fs = hugofs.NewDefault(config.New()) 969 970 os.MkdirAll(filepath.Join(workingDir, "content", "blog"), 0777) 971 972 b.WithWorkingDir(workingDir).WithConfigFile("toml", tomlConfig) 973 974 return test{ 975 b: b, 976 clean: clean, 977 workingDir: workingDir, 978 } 979 } 980 981 c.Run("Default", func(c *qt.C) { 982 mounts := `` 983 984 test := prepare(c, mounts) 985 b := test.b 986 defer test.clean() 987 988 b.WithContent("blog/p1.md", `--- 989 title: P1 990 ---`) 991 992 b.Build(BuildCfg{}) 993 994 p := b.GetPage("blog/p1.md") 995 f := p.File().FileInfo().Meta() 996 b.Assert(filepath.ToSlash(f.Path), qt.Equals, "blog/p1.md") 997 b.Assert(filepath.ToSlash(f.PathFile()), qt.Equals, "content/blog/p1.md") 998 999 b.Assert(b.H.BaseFs.Layouts.Path(filepath.Join(test.workingDir, "layouts", "_default", "single.html")), qt.Equals, filepath.FromSlash("_default/single.html")) 1000 }) 1001 1002 c.Run("Mounts", func(c *qt.C) { 1003 absDir, clean, err := htesting.CreateTempDir(hugofs.Os, "hugo-mounts-paths-abs") 1004 c.Assert(err, qt.IsNil) 1005 defer clean() 1006 1007 mounts := `[module] 1008 [[module.mounts]] 1009 source = "README.md" 1010 target = "content/_index.md" 1011 [[module.mounts]] 1012 source = "mycontent" 1013 target = "content/blog" 1014 [[module.mounts]] 1015 source = "subdir/mypartials" 1016 target = "layouts/partials" 1017 [[module.mounts]] 1018 source = %q 1019 target = "layouts/shortcodes" 1020 ` 1021 mounts = fmt.Sprintf(mounts, filepath.Join(absDir, "/abs/myshortcodes")) 1022 1023 test := prepare(c, mounts) 1024 b := test.b 1025 defer test.clean() 1026 1027 subContentDir := filepath.Join(test.workingDir, "mycontent", "sub") 1028 os.MkdirAll(subContentDir, 0777) 1029 myPartialsDir := filepath.Join(test.workingDir, "subdir", "mypartials") 1030 os.MkdirAll(myPartialsDir, 0777) 1031 1032 absShortcodesDir := filepath.Join(absDir, "abs", "myshortcodes") 1033 os.MkdirAll(absShortcodesDir, 0777) 1034 1035 b.WithSourceFile("README.md", "---\ntitle: Readme\n---") 1036 b.WithSourceFile("mycontent/sub/p1.md", "---\ntitle: P1\n---") 1037 1038 b.WithSourceFile(filepath.Join(absShortcodesDir, "myshort.html"), "MYSHORT") 1039 b.WithSourceFile(filepath.Join(myPartialsDir, "mypartial.html"), "MYPARTIAL") 1040 1041 b.Build(BuildCfg{}) 1042 1043 p1_1 := b.GetPage("/blog/sub/p1.md") 1044 p1_2 := b.GetPage("/mycontent/sub/p1.md") 1045 b.Assert(p1_1, qt.Not(qt.IsNil)) 1046 b.Assert(p1_2, qt.Equals, p1_1) 1047 1048 f := p1_1.File().FileInfo().Meta() 1049 b.Assert(filepath.ToSlash(f.Path), qt.Equals, "blog/sub/p1.md") 1050 b.Assert(filepath.ToSlash(f.PathFile()), qt.Equals, "mycontent/sub/p1.md") 1051 b.Assert(b.H.BaseFs.Layouts.Path(filepath.Join(myPartialsDir, "mypartial.html")), qt.Equals, filepath.FromSlash("partials/mypartial.html")) 1052 b.Assert(b.H.BaseFs.Layouts.Path(filepath.Join(absShortcodesDir, "myshort.html")), qt.Equals, filepath.FromSlash("shortcodes/myshort.html")) 1053 b.Assert(b.H.BaseFs.Content.Path(filepath.Join(subContentDir, "p1.md")), qt.Equals, filepath.FromSlash("blog/sub/p1.md")) 1054 b.Assert(b.H.BaseFs.Content.Path(filepath.Join(test.workingDir, "README.md")), qt.Equals, filepath.FromSlash("_index.md")) 1055 }) 1056 } 1057 1058 // https://github.com/gohugoio/hugo/issues/6299 1059 func TestSiteWithGoModButNoModules(t *testing.T) { 1060 t.Parallel() 1061 1062 c := qt.New(t) 1063 // We need to use the OS fs for this. 1064 workDir, clean, err := htesting.CreateTempDir(hugofs.Os, "hugo-no-mod") 1065 c.Assert(err, qt.IsNil) 1066 1067 cfg := config.New() 1068 cfg.Set("workingDir", workDir) 1069 fs := hugofs.NewFrom(hugofs.Os, cfg) 1070 1071 defer clean() 1072 1073 b := newTestSitesBuilder(t) 1074 b.Fs = fs 1075 1076 b.WithWorkingDir(workDir).WithViper(cfg) 1077 1078 b.WithSourceFile("go.mod", "") 1079 b.Build(BuildCfg{}) 1080 } 1081 1082 // https://github.com/gohugoio/hugo/issues/6622 1083 func TestModuleAbsMount(t *testing.T) { 1084 t.Parallel() 1085 1086 c := qt.New(t) 1087 // We need to use the OS fs for this. 1088 workDir, clean1, err := htesting.CreateTempDir(hugofs.Os, "hugo-project") 1089 c.Assert(err, qt.IsNil) 1090 absContentDir, clean2, err := htesting.CreateTempDir(hugofs.Os, "hugo-content") 1091 c.Assert(err, qt.IsNil) 1092 1093 cfg := config.New() 1094 cfg.Set("workingDir", workDir) 1095 fs := hugofs.NewFrom(hugofs.Os, cfg) 1096 1097 config := fmt.Sprintf(` 1098 workingDir=%q 1099 1100 [module] 1101 [[module.mounts]] 1102 source = %q 1103 target = "content" 1104 1105 `, workDir, absContentDir) 1106 1107 defer clean1() 1108 defer clean2() 1109 1110 b := newTestSitesBuilder(t) 1111 b.Fs = fs 1112 1113 contentFilename := filepath.Join(absContentDir, "p1.md") 1114 afero.WriteFile(hugofs.Os, contentFilename, []byte(` 1115 --- 1116 title: Abs 1117 --- 1118 1119 Content. 1120 `), 0777) 1121 1122 b.WithWorkingDir(workDir).WithConfigFile("toml", config) 1123 b.WithContent("dummy.md", "") 1124 1125 b.WithTemplatesAdded("index.html", ` 1126 {{ $p1 := site.GetPage "p1" }} 1127 P1: {{ $p1.Title }}|{{ $p1.RelPermalink }}|Filename: {{ $p1.File.Filename }} 1128 `) 1129 1130 b.Build(BuildCfg{}) 1131 1132 b.AssertFileContent("public/index.html", "P1: Abs|/p1/", "Filename: "+contentFilename) 1133 }