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  }