hugo

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

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

hugo_modules_test.go (29641B)

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