github.com/olliephillips/hugo@v0.42.2/hugolib/page_bundler_test.go (about)

     1  // Copyright 2017-present 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  	"io/ioutil"
    18  
    19  	"github.com/gohugoio/hugo/common/loggers"
    20  
    21  	"os"
    22  	"runtime"
    23  	"strings"
    24  	"testing"
    25  
    26  	"github.com/gohugoio/hugo/helpers"
    27  
    28  	"io"
    29  
    30  	"github.com/spf13/afero"
    31  
    32  	"github.com/gohugoio/hugo/media"
    33  
    34  	"path/filepath"
    35  
    36  	"fmt"
    37  
    38  	"github.com/gohugoio/hugo/deps"
    39  	"github.com/gohugoio/hugo/hugofs"
    40  	"github.com/gohugoio/hugo/resource"
    41  	"github.com/spf13/viper"
    42  
    43  	"github.com/stretchr/testify/require"
    44  )
    45  
    46  func TestPageBundlerSiteRegular(t *testing.T) {
    47  	t.Parallel()
    48  
    49  	for _, ugly := range []bool{false, true} {
    50  		t.Run(fmt.Sprintf("ugly=%t", ugly),
    51  			func(t *testing.T) {
    52  
    53  				assert := require.New(t)
    54  				fs, cfg := newTestBundleSources(t)
    55  				assert.NoError(loadDefaultSettingsFor(cfg))
    56  				assert.NoError(loadLanguageSettings(cfg, nil))
    57  
    58  				cfg.Set("permalinks", map[string]string{
    59  					"a": ":sections/:filename",
    60  					"b": ":year/:slug/",
    61  					"c": ":sections/:slug",
    62  					"":  ":filename/",
    63  				})
    64  
    65  				cfg.Set("outputFormats", map[string]interface{}{
    66  					"CUSTOMO": map[string]interface{}{
    67  						"mediaType": media.HTMLType,
    68  						"baseName":  "cindex",
    69  						"path":      "cpath",
    70  					},
    71  				})
    72  
    73  				cfg.Set("outputs", map[string]interface{}{
    74  					"home":    []string{"HTML", "CUSTOMO"},
    75  					"page":    []string{"HTML", "CUSTOMO"},
    76  					"section": []string{"HTML", "CUSTOMO"},
    77  				})
    78  
    79  				cfg.Set("uglyURLs", ugly)
    80  
    81  				s := buildSingleSite(t, deps.DepsCfg{Logger: loggers.NewWarningLogger(), Fs: fs, Cfg: cfg}, BuildCfg{})
    82  
    83  				th := testHelper{s.Cfg, s.Fs, t}
    84  
    85  				assert.Len(s.RegularPages, 8)
    86  
    87  				singlePage := s.getPage(KindPage, "a/1.md")
    88  				assert.Equal("", singlePage.BundleType())
    89  
    90  				assert.NotNil(singlePage)
    91  				assert.Equal(singlePage, s.getPage("page", "a/1"))
    92  				assert.Equal(singlePage, s.getPage("page", "1"))
    93  
    94  				assert.Contains(singlePage.content(), "TheContent")
    95  
    96  				if ugly {
    97  					assert.Equal("/a/1.html", singlePage.RelPermalink())
    98  					th.assertFileContent(filepath.FromSlash("/work/public/a/1.html"), "TheContent")
    99  
   100  				} else {
   101  					assert.Equal("/a/1/", singlePage.RelPermalink())
   102  					th.assertFileContent(filepath.FromSlash("/work/public/a/1/index.html"), "TheContent")
   103  				}
   104  
   105  				th.assertFileContent(filepath.FromSlash("/work/public/images/hugo-logo.png"), "content")
   106  
   107  				// This should be just copied to destination.
   108  				th.assertFileContent(filepath.FromSlash("/work/public/assets/pic1.png"), "content")
   109  
   110  				leafBundle1 := s.getPage(KindPage, "b/my-bundle/index.md")
   111  				assert.NotNil(leafBundle1)
   112  				assert.Equal("leaf", leafBundle1.BundleType())
   113  				assert.Equal("b", leafBundle1.Section())
   114  				sectionB := s.getPage(KindSection, "b")
   115  				assert.NotNil(sectionB)
   116  				home, _ := s.Info.Home()
   117  				assert.Equal("branch", home.BundleType())
   118  
   119  				// This is a root bundle and should live in the "home section"
   120  				// See https://github.com/gohugoio/hugo/issues/4332
   121  				rootBundle := s.getPage(KindPage, "root")
   122  				assert.NotNil(rootBundle)
   123  				assert.True(rootBundle.Parent().IsHome())
   124  				if ugly {
   125  					assert.Equal("/root.html", rootBundle.RelPermalink())
   126  				} else {
   127  					assert.Equal("/root/", rootBundle.RelPermalink())
   128  				}
   129  
   130  				leafBundle2 := s.getPage(KindPage, "a/b/index.md")
   131  				assert.NotNil(leafBundle2)
   132  				unicodeBundle := s.getPage(KindPage, "c/bundle/index.md")
   133  				assert.NotNil(unicodeBundle)
   134  
   135  				pageResources := leafBundle1.Resources.ByType(pageResourceType)
   136  				assert.Len(pageResources, 2)
   137  				firstPage := pageResources[0].(*Page)
   138  				secondPage := pageResources[1].(*Page)
   139  				assert.Equal(filepath.FromSlash("b/my-bundle/1.md"), firstPage.pathOrTitle(), secondPage.pathOrTitle())
   140  				assert.Contains(firstPage.content(), "TheContent")
   141  				assert.Equal(6, len(leafBundle1.Resources))
   142  
   143  				// Verify shortcode in bundled page
   144  				assert.Contains(secondPage.content(), filepath.FromSlash("MyShort in b/my-bundle/2.md"))
   145  
   146  				// https://github.com/gohugoio/hugo/issues/4582
   147  				assert.Equal(leafBundle1, firstPage.Parent())
   148  				assert.Equal(leafBundle1, secondPage.Parent())
   149  
   150  				assert.Equal(firstPage, pageResources.GetByPrefix("1"))
   151  				assert.Equal(secondPage, pageResources.GetByPrefix("2"))
   152  				assert.Nil(pageResources.GetByPrefix("doesnotexist"))
   153  
   154  				imageResources := leafBundle1.Resources.ByType("image")
   155  				assert.Equal(3, len(imageResources))
   156  				image := imageResources[0]
   157  
   158  				altFormat := leafBundle1.OutputFormats().Get("CUSTOMO")
   159  				assert.NotNil(altFormat)
   160  
   161  				assert.Equal(filepath.FromSlash("/work/base/b/my-bundle/c/logo.png"), image.(resource.Source).AbsSourceFilename())
   162  				assert.Equal("https://example.com/2017/pageslug/c/logo.png", image.Permalink())
   163  
   164  				th.assertFileContent(filepath.FromSlash("/work/public/2017/pageslug/c/logo.png"), "content")
   165  				th.assertFileContent(filepath.FromSlash("/work/public/cpath/2017/pageslug/c/logo.png"), "content")
   166  
   167  				// Custom media type defined in site config.
   168  				assert.Len(leafBundle1.Resources.ByType("bepsays"), 1)
   169  
   170  				if ugly {
   171  					assert.Equal("/2017/pageslug.html", leafBundle1.RelPermalink())
   172  					th.assertFileContent(filepath.FromSlash("/work/public/2017/pageslug.html"),
   173  						"TheContent",
   174  						"Sunset RelPermalink: /2017/pageslug/sunset1.jpg",
   175  						"Thumb Width: 123",
   176  						"Thumb Name: my-sunset-1",
   177  						"Short Sunset RelPermalink: /2017/pageslug/sunset2.jpg",
   178  						"Short Thumb Width: 56",
   179  						"1: Image Title: Sunset Galore 1",
   180  						"1: Image Params: map[myparam:My Sunny Param]",
   181  						"2: Image Title: Sunset Galore 2",
   182  						"2: Image Params: map[myparam:My Sunny Param]",
   183  						"1: Image myParam: Lower: My Sunny Param Caps: My Sunny Param",
   184  					)
   185  					th.assertFileContent(filepath.FromSlash("/work/public/cpath/2017/pageslug.html"), "TheContent")
   186  
   187  					assert.Equal("/a/b.html", leafBundle2.RelPermalink())
   188  
   189  					// 은행
   190  					assert.Equal("/c/%EC%9D%80%ED%96%89.html", unicodeBundle.RelPermalink())
   191  					th.assertFileContent(filepath.FromSlash("/work/public/c/은행.html"), "Content for 은행")
   192  					th.assertFileContent(filepath.FromSlash("/work/public/c/은행/logo-은행.png"), "은행 PNG")
   193  
   194  				} else {
   195  					assert.Equal("/2017/pageslug/", leafBundle1.RelPermalink())
   196  					th.assertFileContent(filepath.FromSlash("/work/public/2017/pageslug/index.html"), "TheContent")
   197  					th.assertFileContent(filepath.FromSlash("/work/public/cpath/2017/pageslug/cindex.html"), "TheContent")
   198  					th.assertFileContent(filepath.FromSlash("/work/public/2017/pageslug/index.html"), "Single Title")
   199  					th.assertFileContent(filepath.FromSlash("/work/public/root/index.html"), "Single Title")
   200  
   201  					assert.Equal("/a/b/", leafBundle2.RelPermalink())
   202  
   203  				}
   204  
   205  			})
   206  	}
   207  
   208  }
   209  
   210  func TestPageBundlerSiteMultilingual(t *testing.T) {
   211  	t.Parallel()
   212  
   213  	for _, ugly := range []bool{false, true} {
   214  		t.Run(fmt.Sprintf("ugly=%t", ugly),
   215  			func(t *testing.T) {
   216  
   217  				assert := require.New(t)
   218  				fs, cfg := newTestBundleSourcesMultilingual(t)
   219  				cfg.Set("uglyURLs", ugly)
   220  
   221  				assert.NoError(loadDefaultSettingsFor(cfg))
   222  				assert.NoError(loadLanguageSettings(cfg, nil))
   223  				sites, err := NewHugoSites(deps.DepsCfg{Fs: fs, Cfg: cfg})
   224  				assert.NoError(err)
   225  				assert.Equal(2, len(sites.Sites))
   226  
   227  				assert.NoError(sites.Build(BuildCfg{}))
   228  
   229  				s := sites.Sites[0]
   230  
   231  				assert.Equal(8, len(s.RegularPages))
   232  				assert.Equal(16, len(s.Pages))
   233  				assert.Equal(31, len(s.AllPages))
   234  
   235  				bundleWithSubPath := s.getPage(KindPage, "lb/index")
   236  				assert.NotNil(bundleWithSubPath)
   237  
   238  				// See https://github.com/gohugoio/hugo/issues/4312
   239  				// Before that issue:
   240  				// A bundle in a/b/index.en.md
   241  				// a/b/index.en.md => OK
   242  				// a/b/index => OK
   243  				// index.en.md => ambigous, but OK.
   244  				// With bundles, the file name has little meaning, the folder it lives in does. So this should also work:
   245  				// a/b
   246  				// and probably also just b (aka "my-bundle")
   247  				// These may also be translated, so we also need to test that.
   248  				//  "bf", "my-bf-bundle", "index.md + nn
   249  				bfBundle := s.getPage(KindPage, "bf/my-bf-bundle/index")
   250  				assert.NotNil(bfBundle)
   251  				assert.Equal("en", bfBundle.Lang())
   252  				assert.Equal(bfBundle, s.getPage(KindPage, "bf/my-bf-bundle/index.md"))
   253  				assert.Equal(bfBundle, s.getPage(KindPage, "bf/my-bf-bundle"))
   254  				assert.Equal(bfBundle, s.getPage(KindPage, "my-bf-bundle"))
   255  
   256  				nnSite := sites.Sites[1]
   257  				assert.Equal(7, len(nnSite.RegularPages))
   258  
   259  				bfBundleNN := nnSite.getPage(KindPage, "bf/my-bf-bundle/index")
   260  				assert.NotNil(bfBundleNN)
   261  				assert.Equal("nn", bfBundleNN.Lang())
   262  				assert.Equal(bfBundleNN, nnSite.getPage(KindPage, "bf/my-bf-bundle/index.nn.md"))
   263  				assert.Equal(bfBundleNN, nnSite.getPage(KindPage, "bf/my-bf-bundle"))
   264  				assert.Equal(bfBundleNN, nnSite.getPage(KindPage, "my-bf-bundle"))
   265  
   266  				// See https://github.com/gohugoio/hugo/issues/4295
   267  				// Every resource should have its Name prefixed with its base folder.
   268  				cBundleResources := bundleWithSubPath.Resources.ByPrefix("c/")
   269  				assert.Equal(4, len(cBundleResources))
   270  				bundlePage := bundleWithSubPath.Resources.GetByPrefix("c/page")
   271  				assert.NotNil(bundlePage)
   272  				assert.IsType(&Page{}, bundlePage)
   273  
   274  			})
   275  	}
   276  }
   277  
   278  func TestMultilingualDisableDefaultLanguage(t *testing.T) {
   279  	t.Parallel()
   280  
   281  	assert := require.New(t)
   282  	_, cfg := newTestBundleSourcesMultilingual(t)
   283  
   284  	cfg.Set("disableLanguages", []string{"en"})
   285  
   286  	err := loadDefaultSettingsFor(cfg)
   287  	assert.NoError(err)
   288  	err = loadLanguageSettings(cfg, nil)
   289  	assert.Error(err)
   290  	assert.Contains(err.Error(), "cannot disable default language")
   291  }
   292  
   293  func TestMultilingualDisableLanguage(t *testing.T) {
   294  	t.Parallel()
   295  
   296  	assert := require.New(t)
   297  	fs, cfg := newTestBundleSourcesMultilingual(t)
   298  	cfg.Set("disableLanguages", []string{"nn"})
   299  
   300  	assert.NoError(loadDefaultSettingsFor(cfg))
   301  	assert.NoError(loadLanguageSettings(cfg, nil))
   302  
   303  	sites, err := NewHugoSites(deps.DepsCfg{Fs: fs, Cfg: cfg})
   304  	assert.NoError(err)
   305  	assert.Equal(1, len(sites.Sites))
   306  
   307  	assert.NoError(sites.Build(BuildCfg{}))
   308  
   309  	s := sites.Sites[0]
   310  
   311  	assert.Equal(8, len(s.RegularPages))
   312  	assert.Equal(16, len(s.Pages))
   313  	// No nn pages
   314  	assert.Equal(16, len(s.AllPages))
   315  	for _, p := range s.rawAllPages {
   316  		assert.True(p.Lang() != "nn")
   317  	}
   318  	for _, p := range s.AllPages {
   319  		assert.True(p.Lang() != "nn")
   320  	}
   321  
   322  }
   323  
   324  func TestPageBundlerSiteWitSymbolicLinksInContent(t *testing.T) {
   325  	if runtime.GOOS == "windows" && os.Getenv("CI") == "" {
   326  		t.Skip("Skip TestPageBundlerSiteWitSymbolicLinksInContent as os.Symlink needs administrator rights on Windows")
   327  	}
   328  
   329  	assert := require.New(t)
   330  	ps, workDir := newTestBundleSymbolicSources(t)
   331  	cfg := ps.Cfg
   332  	fs := ps.Fs
   333  
   334  	s := buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg, Logger: loggers.NewErrorLogger()}, BuildCfg{})
   335  
   336  	th := testHelper{s.Cfg, s.Fs, t}
   337  
   338  	assert.Equal(7, len(s.RegularPages))
   339  	a1Bundle := s.getPage(KindPage, "symbolic2/a1/index.md")
   340  	assert.NotNil(a1Bundle)
   341  	assert.Equal(2, len(a1Bundle.Resources))
   342  	assert.Equal(1, len(a1Bundle.Resources.ByType(pageResourceType)))
   343  
   344  	th.assertFileContent(filepath.FromSlash(workDir+"/public/a/page/index.html"), "TheContent")
   345  	th.assertFileContent(filepath.FromSlash(workDir+"/public/symbolic1/s1/index.html"), "TheContent")
   346  	th.assertFileContent(filepath.FromSlash(workDir+"/public/symbolic2/a1/index.html"), "TheContent")
   347  
   348  }
   349  
   350  func TestPageBundlerHeadless(t *testing.T) {
   351  	t.Parallel()
   352  
   353  	cfg, fs := newTestCfg()
   354  	assert := require.New(t)
   355  
   356  	workDir := "/work"
   357  	cfg.Set("workingDir", workDir)
   358  	cfg.Set("contentDir", "base")
   359  	cfg.Set("baseURL", "https://example.com")
   360  
   361  	pageContent := `---
   362  title: "Bundle Galore"
   363  slug: s1
   364  date: 2017-01-23
   365  ---
   366  
   367  TheContent.
   368  
   369  {{< myShort >}}
   370  `
   371  
   372  	writeSource(t, fs, filepath.Join(workDir, "layouts", "_default", "single.html"), "single {{ .Content }}")
   373  	writeSource(t, fs, filepath.Join(workDir, "layouts", "_default", "list.html"), "list")
   374  	writeSource(t, fs, filepath.Join(workDir, "layouts", "shortcodes", "myShort.html"), "SHORTCODE")
   375  
   376  	writeSource(t, fs, filepath.Join(workDir, "base", "a", "index.md"), pageContent)
   377  	writeSource(t, fs, filepath.Join(workDir, "base", "a", "l1.png"), "PNG image")
   378  	writeSource(t, fs, filepath.Join(workDir, "base", "a", "l2.png"), "PNG image")
   379  
   380  	writeSource(t, fs, filepath.Join(workDir, "base", "b", "index.md"), `---
   381  title: "Headless Bundle in Topless Bar"
   382  slug: s2
   383  headless: true
   384  date: 2017-01-23
   385  ---
   386  
   387  TheContent.
   388  HEADLESS {{< myShort >}}
   389  `)
   390  	writeSource(t, fs, filepath.Join(workDir, "base", "b", "l1.png"), "PNG image")
   391  	writeSource(t, fs, filepath.Join(workDir, "base", "b", "l2.png"), "PNG image")
   392  	writeSource(t, fs, filepath.Join(workDir, "base", "b", "p1.md"), pageContent)
   393  
   394  	s := buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{})
   395  
   396  	assert.Equal(1, len(s.RegularPages))
   397  	assert.Equal(1, len(s.headlessPages))
   398  
   399  	regular := s.getPage(KindPage, "a/index")
   400  	assert.Equal("/a/s1/", regular.RelPermalink())
   401  
   402  	headless := s.getPage(KindPage, "b/index")
   403  	assert.NotNil(headless)
   404  	assert.True(headless.headless)
   405  	assert.Equal("Headless Bundle in Topless Bar", headless.Title())
   406  	assert.Equal("", headless.RelPermalink())
   407  	assert.Equal("", headless.Permalink())
   408  	assert.Contains(headless.content(), "HEADLESS SHORTCODE")
   409  
   410  	headlessResources := headless.Resources
   411  	assert.Equal(3, len(headlessResources))
   412  	assert.Equal(2, len(headlessResources.Match("l*")))
   413  	pageResource := headlessResources.GetMatch("p*")
   414  	assert.NotNil(pageResource)
   415  	assert.IsType(&Page{}, pageResource)
   416  	p := pageResource.(*Page)
   417  	assert.Contains(p.content(), "SHORTCODE")
   418  	assert.Equal("p1.md", p.Name())
   419  
   420  	th := testHelper{s.Cfg, s.Fs, t}
   421  
   422  	th.assertFileContent(filepath.FromSlash(workDir+"/public/a/s1/index.html"), "TheContent")
   423  	th.assertFileContent(filepath.FromSlash(workDir+"/public/a/s1/l1.png"), "PNG")
   424  
   425  	th.assertFileNotExist(workDir + "/public/b/s2/index.html")
   426  	// But the bundled resources needs to be published
   427  	th.assertFileContent(filepath.FromSlash(workDir+"/public/b/s2/l1.png"), "PNG")
   428  
   429  }
   430  
   431  func newTestBundleSources(t *testing.T) (*hugofs.Fs, *viper.Viper) {
   432  	cfg, fs := newTestCfg()
   433  	assert := require.New(t)
   434  
   435  	workDir := "/work"
   436  	cfg.Set("workingDir", workDir)
   437  	cfg.Set("contentDir", "base")
   438  	cfg.Set("baseURL", "https://example.com")
   439  	cfg.Set("mediaTypes", map[string]interface{}{
   440  		"text/bepsays": map[string]interface{}{
   441  			"suffix": "bep",
   442  		},
   443  	})
   444  
   445  	pageContent := `---
   446  title: "Bundle Galore"
   447  slug: pageslug
   448  date: 2017-10-09
   449  ---
   450  
   451  TheContent.
   452  `
   453  
   454  	pageContentShortcode := `---
   455  title: "Bundle Galore"
   456  slug: pageslug
   457  date: 2017-10-09
   458  ---
   459  
   460  TheContent.
   461  
   462  {{< myShort >}}
   463  `
   464  
   465  	pageWithImageShortcodeAndResourceMetadataContent := `---
   466  title: "Bundle Galore"
   467  slug: pageslug
   468  date: 2017-10-09
   469  resources:
   470  - src: "*.jpg"
   471    name: "my-sunset-:counter"
   472    title: "Sunset Galore :counter"
   473    params:
   474      myParam: "My Sunny Param"
   475  ---
   476  
   477  TheContent.
   478  
   479  {{< myShort >}}
   480  `
   481  
   482  	pageContentNoSlug := `---
   483  title: "Bundle Galore #2"
   484  date: 2017-10-09
   485  ---
   486  
   487  TheContent.
   488  `
   489  
   490  	singleLayout := `
   491  Single Title: {{ .Title }}
   492  Content: {{ .Content }}
   493  {{ $sunset := .Resources.GetByPrefix "my-sunset-1" }}
   494  {{ with $sunset }}
   495  Sunset RelPermalink: {{ .RelPermalink }}
   496  {{ $thumb := .Fill "123x123" }}
   497  Thumb Width: {{ $thumb.Width }}
   498  Thumb Name: {{ $thumb.Name }}
   499  Thumb Title: {{ $thumb.Title }}
   500  Thumb RelPermalink: {{ $thumb.RelPermalink }}
   501  {{ end }}
   502  {{ range $i, $e := .Resources.ByType "image" }}
   503  {{ $i }}: Image Title: {{ .Title }}
   504  {{ $i }}: Image Name: {{ .Name }}
   505  {{ $i }}: Image Params: {{ printf "%v" .Params }}
   506  {{ $i }}: Image myParam: Lower: {{ .Params.myparam }} Caps: {{ .Params.MYPARAM }}
   507  {{ end }}
   508  `
   509  
   510  	myShort := `
   511  MyShort in {{ .Page.Path }}:
   512  {{ $sunset := .Page.Resources.GetByPrefix "my-sunset-2" }}
   513  {{ with $sunset }}
   514  Short Sunset RelPermalink: {{ .RelPermalink }}
   515  {{ $thumb := .Fill "56x56" }}
   516  Short Thumb Width: {{ $thumb.Width }}
   517  {{ end }}
   518  `
   519  
   520  	listLayout := `{{ .Title }}|{{ .Content }}`
   521  
   522  	writeSource(t, fs, filepath.Join(workDir, "layouts", "_default", "single.html"), singleLayout)
   523  	writeSource(t, fs, filepath.Join(workDir, "layouts", "_default", "list.html"), listLayout)
   524  	writeSource(t, fs, filepath.Join(workDir, "layouts", "shortcodes", "myShort.html"), myShort)
   525  
   526  	writeSource(t, fs, filepath.Join(workDir, "base", "_index.md"), pageContent)
   527  	writeSource(t, fs, filepath.Join(workDir, "base", "_1.md"), pageContent)
   528  	writeSource(t, fs, filepath.Join(workDir, "base", "_1.png"), pageContent)
   529  
   530  	writeSource(t, fs, filepath.Join(workDir, "base", "images", "hugo-logo.png"), "content")
   531  	writeSource(t, fs, filepath.Join(workDir, "base", "a", "2.md"), pageContent)
   532  	writeSource(t, fs, filepath.Join(workDir, "base", "a", "1.md"), pageContent)
   533  
   534  	writeSource(t, fs, filepath.Join(workDir, "base", "a", "b", "index.md"), pageContentNoSlug)
   535  	writeSource(t, fs, filepath.Join(workDir, "base", "a", "b", "ab1.md"), pageContentNoSlug)
   536  
   537  	// Mostly plain static assets in a folder with a page in a sub folder thrown in.
   538  	writeSource(t, fs, filepath.Join(workDir, "base", "assets", "pic1.png"), "content")
   539  	writeSource(t, fs, filepath.Join(workDir, "base", "assets", "pic2.png"), "content")
   540  	writeSource(t, fs, filepath.Join(workDir, "base", "assets", "pages", "mypage.md"), pageContent)
   541  
   542  	// Bundle
   543  	writeSource(t, fs, filepath.Join(workDir, "base", "b", "my-bundle", "index.md"), pageWithImageShortcodeAndResourceMetadataContent)
   544  	writeSource(t, fs, filepath.Join(workDir, "base", "b", "my-bundle", "1.md"), pageContent)
   545  	writeSource(t, fs, filepath.Join(workDir, "base", "b", "my-bundle", "2.md"), pageContentShortcode)
   546  	writeSource(t, fs, filepath.Join(workDir, "base", "b", "my-bundle", "custom-mime.bep"), "bepsays")
   547  	writeSource(t, fs, filepath.Join(workDir, "base", "b", "my-bundle", "c", "logo.png"), "content")
   548  
   549  	// Bundle with 은행 slug
   550  	// See https://github.com/gohugoio/hugo/issues/4241
   551  	writeSource(t, fs, filepath.Join(workDir, "base", "c", "bundle", "index.md"), `---
   552  title: "은행 은행"
   553  slug: 은행
   554  date: 2017-10-09
   555  ---
   556  
   557  Content for 은행.
   558  `)
   559  
   560  	// Bundle in root
   561  	writeSource(t, fs, filepath.Join(workDir, "base", "root", "index.md"), pageWithImageShortcodeAndResourceMetadataContent)
   562  	writeSource(t, fs, filepath.Join(workDir, "base", "root", "1.md"), pageContent)
   563  	writeSource(t, fs, filepath.Join(workDir, "base", "root", "c", "logo.png"), "content")
   564  
   565  	writeSource(t, fs, filepath.Join(workDir, "base", "c", "bundle", "logo-은행.png"), "은행 PNG")
   566  
   567  	// Write a real image into one of the bundle above.
   568  	src, err := os.Open("testdata/sunset.jpg")
   569  	assert.NoError(err)
   570  
   571  	// We need 2 to test https://github.com/gohugoio/hugo/issues/4202
   572  	out, err := fs.Source.Create(filepath.Join(workDir, "base", "b", "my-bundle", "sunset1.jpg"))
   573  	assert.NoError(err)
   574  	out2, err := fs.Source.Create(filepath.Join(workDir, "base", "b", "my-bundle", "sunset2.jpg"))
   575  	assert.NoError(err)
   576  
   577  	_, err = io.Copy(out, src)
   578  	out.Close()
   579  	src.Seek(0, 0)
   580  	_, err = io.Copy(out2, src)
   581  	out2.Close()
   582  	src.Close()
   583  	assert.NoError(err)
   584  
   585  	return fs, cfg
   586  
   587  }
   588  
   589  func newTestBundleSourcesMultilingual(t *testing.T) (*hugofs.Fs, *viper.Viper) {
   590  	cfg, fs := newTestCfg()
   591  
   592  	workDir := "/work"
   593  	cfg.Set("workingDir", workDir)
   594  	cfg.Set("contentDir", "base")
   595  	cfg.Set("baseURL", "https://example.com")
   596  	cfg.Set("defaultContentLanguage", "en")
   597  
   598  	langConfig := map[string]interface{}{
   599  		"en": map[string]interface{}{
   600  			"weight":       1,
   601  			"languageName": "English",
   602  		},
   603  		"nn": map[string]interface{}{
   604  			"weight":       2,
   605  			"languageName": "Nynorsk",
   606  		},
   607  	}
   608  
   609  	cfg.Set("languages", langConfig)
   610  
   611  	pageContent := `---
   612  slug: pageslug
   613  date: 2017-10-09
   614  ---
   615  
   616  TheContent.
   617  `
   618  
   619  	layout := `{{ .Title }}|{{ .Content }}|Lang: {{ .Site.Language.Lang }}`
   620  
   621  	writeSource(t, fs, filepath.Join(workDir, "layouts", "_default", "single.html"), layout)
   622  	writeSource(t, fs, filepath.Join(workDir, "layouts", "_default", "list.html"), layout)
   623  
   624  	writeSource(t, fs, filepath.Join(workDir, "base", "1s", "mypage.md"), pageContent)
   625  	writeSource(t, fs, filepath.Join(workDir, "base", "1s", "mypage.nn.md"), pageContent)
   626  	writeSource(t, fs, filepath.Join(workDir, "base", "1s", "mylogo.png"), "content")
   627  
   628  	writeSource(t, fs, filepath.Join(workDir, "base", "bb", "_index.md"), pageContent)
   629  	writeSource(t, fs, filepath.Join(workDir, "base", "bb", "_index.nn.md"), pageContent)
   630  	writeSource(t, fs, filepath.Join(workDir, "base", "bb", "en.md"), pageContent)
   631  	writeSource(t, fs, filepath.Join(workDir, "base", "bb", "_1.md"), pageContent)
   632  	writeSource(t, fs, filepath.Join(workDir, "base", "bb", "_1.nn.md"), pageContent)
   633  	writeSource(t, fs, filepath.Join(workDir, "base", "bb", "a.png"), "content")
   634  	writeSource(t, fs, filepath.Join(workDir, "base", "bb", "b.png"), "content")
   635  	writeSource(t, fs, filepath.Join(workDir, "base", "bb", "b.nn.png"), "content")
   636  	writeSource(t, fs, filepath.Join(workDir, "base", "bb", "c.nn.png"), "content")
   637  	writeSource(t, fs, filepath.Join(workDir, "base", "bb", "b", "d.nn.png"), "content")
   638  
   639  	writeSource(t, fs, filepath.Join(workDir, "base", "bc", "_index.md"), pageContent)
   640  	writeSource(t, fs, filepath.Join(workDir, "base", "bc", "page.md"), pageContent)
   641  	writeSource(t, fs, filepath.Join(workDir, "base", "bc", "logo-bc.png"), pageContent)
   642  	writeSource(t, fs, filepath.Join(workDir, "base", "bc", "page.nn.md"), pageContent)
   643  
   644  	writeSource(t, fs, filepath.Join(workDir, "base", "bd", "index.md"), pageContent)
   645  	writeSource(t, fs, filepath.Join(workDir, "base", "bd", "page.md"), pageContent)
   646  	writeSource(t, fs, filepath.Join(workDir, "base", "bd", "page.nn.md"), pageContent)
   647  
   648  	writeSource(t, fs, filepath.Join(workDir, "base", "be", "_index.md"), pageContent)
   649  	writeSource(t, fs, filepath.Join(workDir, "base", "be", "page.md"), pageContent)
   650  	writeSource(t, fs, filepath.Join(workDir, "base", "be", "page.nn.md"), pageContent)
   651  
   652  	// Bundle leaf,  multilingual
   653  	writeSource(t, fs, filepath.Join(workDir, "base", "lb", "index.md"), pageContent)
   654  	writeSource(t, fs, filepath.Join(workDir, "base", "lb", "index.nn.md"), pageContent)
   655  	writeSource(t, fs, filepath.Join(workDir, "base", "lb", "1.md"), pageContent)
   656  	writeSource(t, fs, filepath.Join(workDir, "base", "lb", "2.md"), pageContent)
   657  	writeSource(t, fs, filepath.Join(workDir, "base", "lb", "2.nn.md"), pageContent)
   658  	writeSource(t, fs, filepath.Join(workDir, "base", "lb", "c", "page.md"), pageContent)
   659  	writeSource(t, fs, filepath.Join(workDir, "base", "lb", "c", "logo.png"), "content")
   660  	writeSource(t, fs, filepath.Join(workDir, "base", "lb", "c", "logo.nn.png"), "content")
   661  	writeSource(t, fs, filepath.Join(workDir, "base", "lb", "c", "one.png"), "content")
   662  	writeSource(t, fs, filepath.Join(workDir, "base", "lb", "c", "d", "deep.png"), "content")
   663  
   664  	//Translated bundle in some sensible sub path.
   665  	writeSource(t, fs, filepath.Join(workDir, "base", "bf", "my-bf-bundle", "index.md"), pageContent)
   666  	writeSource(t, fs, filepath.Join(workDir, "base", "bf", "my-bf-bundle", "index.nn.md"), pageContent)
   667  	writeSource(t, fs, filepath.Join(workDir, "base", "bf", "my-bf-bundle", "page.md"), pageContent)
   668  
   669  	return fs, cfg
   670  }
   671  
   672  func newTestBundleSymbolicSources(t *testing.T) (*helpers.PathSpec, string) {
   673  	assert := require.New(t)
   674  	// We need to use the OS fs for this.
   675  	cfg := viper.New()
   676  	fs := hugofs.NewFrom(hugofs.Os, cfg)
   677  	fs.Destination = &afero.MemMapFs{}
   678  	loadDefaultSettingsFor(cfg)
   679  
   680  	workDir, err := ioutil.TempDir("", "hugosym")
   681  
   682  	if runtime.GOOS == "darwin" && !strings.HasPrefix(workDir, "/private") {
   683  		// To get the entry folder in line with the rest. This its a little bit
   684  		// mysterious, but so be it.
   685  		workDir = "/private" + workDir
   686  	}
   687  
   688  	contentDir := "base"
   689  	cfg.Set("workingDir", workDir)
   690  	cfg.Set("contentDir", contentDir)
   691  	cfg.Set("baseURL", "https://example.com")
   692  
   693  	if err := loadLanguageSettings(cfg, nil); err != nil {
   694  		t.Fatal(err)
   695  	}
   696  
   697  	layout := `{{ .Title }}|{{ .Content }}`
   698  	pageContent := `---
   699  slug: %s
   700  date: 2017-10-09
   701  ---
   702  
   703  TheContent.
   704  `
   705  
   706  	fs.Source.MkdirAll(filepath.Join(workDir, "layouts", "_default"), 0777)
   707  	fs.Source.MkdirAll(filepath.Join(workDir, contentDir), 0777)
   708  	fs.Source.MkdirAll(filepath.Join(workDir, contentDir, "a"), 0777)
   709  	for i := 1; i <= 3; i++ {
   710  		fs.Source.MkdirAll(filepath.Join(workDir, fmt.Sprintf("symcontent%d", i)), 0777)
   711  
   712  	}
   713  	fs.Source.MkdirAll(filepath.Join(workDir, "symcontent2", "a1"), 0777)
   714  
   715  	writeSource(t, fs, filepath.Join(workDir, "layouts", "_default", "single.html"), layout)
   716  	writeSource(t, fs, filepath.Join(workDir, "layouts", "_default", "list.html"), layout)
   717  
   718  	writeSource(t, fs, filepath.Join(workDir, contentDir, "a", "regular.md"), fmt.Sprintf(pageContent, "a1"))
   719  
   720  	// Regular files inside symlinked folder.
   721  	writeSource(t, fs, filepath.Join(workDir, "symcontent1", "s1.md"), fmt.Sprintf(pageContent, "s1"))
   722  	writeSource(t, fs, filepath.Join(workDir, "symcontent1", "s2.md"), fmt.Sprintf(pageContent, "s2"))
   723  
   724  	// A bundle
   725  	writeSource(t, fs, filepath.Join(workDir, "symcontent2", "a1", "index.md"), fmt.Sprintf(pageContent, ""))
   726  	writeSource(t, fs, filepath.Join(workDir, "symcontent2", "a1", "page.md"), fmt.Sprintf(pageContent, "page"))
   727  	writeSource(t, fs, filepath.Join(workDir, "symcontent2", "a1", "logo.png"), "image")
   728  
   729  	// Assets
   730  	writeSource(t, fs, filepath.Join(workDir, "symcontent3", "s1.png"), "image")
   731  	writeSource(t, fs, filepath.Join(workDir, "symcontent3", "s2.png"), "image")
   732  
   733  	wd, _ := os.Getwd()
   734  	defer func() {
   735  		os.Chdir(wd)
   736  	}()
   737  	// Symlinked sections inside content.
   738  	os.Chdir(filepath.Join(workDir, contentDir))
   739  	for i := 1; i <= 3; i++ {
   740  		assert.NoError(os.Symlink(filepath.FromSlash(fmt.Sprintf(("../symcontent%d"), i)), fmt.Sprintf("symbolic%d", i)))
   741  	}
   742  
   743  	os.Chdir(filepath.Join(workDir, contentDir, "a"))
   744  
   745  	// Create a symlink to one single content file
   746  	assert.NoError(os.Symlink(filepath.FromSlash("../../symcontent2/a1/page.md"), "page_s.md"))
   747  
   748  	os.Chdir(filepath.FromSlash("../../symcontent3"))
   749  
   750  	// Create a circular symlink. Will print some warnings.
   751  	assert.NoError(os.Symlink(filepath.Join("..", contentDir), filepath.FromSlash("circus")))
   752  
   753  	os.Chdir(workDir)
   754  	assert.NoError(err)
   755  
   756  	ps, _ := helpers.NewPathSpec(fs, cfg)
   757  
   758  	return ps, workDir
   759  }