github.com/graemephi/kahugo@v0.62.3-0.20211121071557-d78c0423784d/hugolib/pagebundler_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  	"io"
    19  	"os"
    20  	"path"
    21  	"path/filepath"
    22  	"regexp"
    23  	"strings"
    24  	"testing"
    25  
    26  	"github.com/gohugoio/hugo/config"
    27  
    28  	"github.com/gohugoio/hugo/hugofs/files"
    29  
    30  	"github.com/gohugoio/hugo/helpers"
    31  
    32  	"github.com/gohugoio/hugo/hugofs"
    33  
    34  	"github.com/gohugoio/hugo/common/loggers"
    35  	"github.com/gohugoio/hugo/resources/page"
    36  
    37  	"github.com/gohugoio/hugo/htesting"
    38  
    39  	"github.com/gohugoio/hugo/deps"
    40  
    41  	qt "github.com/frankban/quicktest"
    42  )
    43  
    44  func TestPageBundlerSiteRegular(t *testing.T) {
    45  	c := qt.New(t)
    46  	baseBaseURL := "https://example.com"
    47  
    48  	for _, baseURLPath := range []string{"", "/hugo"} {
    49  		for _, canonify := range []bool{false, true} {
    50  			for _, ugly := range []bool{false, true} {
    51  				baseURLPathId := baseURLPath
    52  				if baseURLPathId == "" {
    53  					baseURLPathId = "NONE"
    54  				}
    55  				ugly := ugly
    56  				canonify := canonify
    57  				c.Run(fmt.Sprintf("ugly=%t,canonify=%t,path=%s", ugly, canonify, baseURLPathId),
    58  					func(c *qt.C) {
    59  						c.Parallel()
    60  						baseURL := baseBaseURL + baseURLPath
    61  						relURLBase := baseURLPath
    62  						if canonify {
    63  							relURLBase = ""
    64  						}
    65  						fs, cfg := newTestBundleSources(c)
    66  						cfg.Set("baseURL", baseURL)
    67  						cfg.Set("canonifyURLs", canonify)
    68  
    69  						cfg.Set("permalinks", map[string]string{
    70  							"a": ":sections/:filename",
    71  							"b": ":year/:slug/",
    72  							"c": ":sections/:slug",
    73  							"/": ":filename/",
    74  						})
    75  
    76  						cfg.Set("outputFormats", map[string]interface{}{
    77  							"CUSTOMO": map[string]interface{}{
    78  								"mediaType":     "text/html",
    79  								"baseName":      "cindex",
    80  								"path":          "cpath",
    81  								"permalinkable": true,
    82  							},
    83  						})
    84  
    85  						cfg.Set("outputs", map[string]interface{}{
    86  							"home":    []string{"HTML", "CUSTOMO"},
    87  							"page":    []string{"HTML", "CUSTOMO"},
    88  							"section": []string{"HTML", "CUSTOMO"},
    89  						})
    90  
    91  						cfg.Set("uglyURLs", ugly)
    92  
    93  						b := newTestSitesBuilderFromDepsCfg(c, deps.DepsCfg{Logger: loggers.NewErrorLogger(), Fs: fs, Cfg: cfg}).WithNothingAdded()
    94  
    95  						b.Build(BuildCfg{})
    96  
    97  						s := b.H.Sites[0]
    98  
    99  						c.Assert(len(s.RegularPages()), qt.Equals, 8)
   100  
   101  						singlePage := s.getPage(page.KindPage, "a/1.md")
   102  						c.Assert(singlePage.BundleType(), qt.Equals, files.ContentClass(""))
   103  
   104  						c.Assert(singlePage, qt.Not(qt.IsNil))
   105  						c.Assert(s.getPage("page", "a/1"), qt.Equals, singlePage)
   106  						c.Assert(s.getPage("page", "1"), qt.Equals, singlePage)
   107  
   108  						c.Assert(content(singlePage), qt.Contains, "TheContent")
   109  
   110  						relFilename := func(basePath, outBase string) (string, string) {
   111  							rel := basePath
   112  							if ugly {
   113  								rel = strings.TrimSuffix(basePath, "/") + ".html"
   114  							}
   115  
   116  							var filename string
   117  							if !ugly {
   118  								filename = path.Join(basePath, outBase)
   119  							} else {
   120  								filename = rel
   121  							}
   122  
   123  							rel = fmt.Sprintf("%s%s", relURLBase, rel)
   124  
   125  							return rel, filename
   126  						}
   127  
   128  						// Check both output formats
   129  						rel, filename := relFilename("/a/1/", "index.html")
   130  						b.AssertFileContent(filepath.Join("/work/public", filename),
   131  							"TheContent",
   132  							"Single RelPermalink: "+rel,
   133  						)
   134  
   135  						rel, filename = relFilename("/cpath/a/1/", "cindex.html")
   136  
   137  						b.AssertFileContent(filepath.Join("/work/public", filename),
   138  							"TheContent",
   139  							"Single RelPermalink: "+rel,
   140  						)
   141  
   142  						b.AssertFileContent(filepath.FromSlash("/work/public/images/hugo-logo.png"), "content")
   143  
   144  						// This should be just copied to destination.
   145  						b.AssertFileContent(filepath.FromSlash("/work/public/assets/pic1.png"), "content")
   146  
   147  						leafBundle1 := s.getPage(page.KindPage, "b/my-bundle/index.md")
   148  						c.Assert(leafBundle1, qt.Not(qt.IsNil))
   149  						c.Assert(leafBundle1.BundleType(), qt.Equals, files.ContentClassLeaf)
   150  						c.Assert(leafBundle1.Section(), qt.Equals, "b")
   151  						sectionB := s.getPage(page.KindSection, "b")
   152  						c.Assert(sectionB, qt.Not(qt.IsNil))
   153  						home, _ := s.Info.Home()
   154  						c.Assert(home.BundleType(), qt.Equals, files.ContentClassBranch)
   155  
   156  						// This is a root bundle and should live in the "home section"
   157  						// See https://github.com/gohugoio/hugo/issues/4332
   158  						rootBundle := s.getPage(page.KindPage, "root")
   159  						c.Assert(rootBundle, qt.Not(qt.IsNil))
   160  						c.Assert(rootBundle.Parent().IsHome(), qt.Equals, true)
   161  						if !ugly {
   162  							b.AssertFileContent(filepath.FromSlash("/work/public/root/index.html"), "Single RelPermalink: "+relURLBase+"/root/")
   163  							b.AssertFileContent(filepath.FromSlash("/work/public/cpath/root/cindex.html"), "Single RelPermalink: "+relURLBase+"/cpath/root/")
   164  						}
   165  
   166  						leafBundle2 := s.getPage(page.KindPage, "a/b/index.md")
   167  						c.Assert(leafBundle2, qt.Not(qt.IsNil))
   168  						unicodeBundle := s.getPage(page.KindPage, "c/bundle/index.md")
   169  						c.Assert(unicodeBundle, qt.Not(qt.IsNil))
   170  
   171  						pageResources := leafBundle1.Resources().ByType(pageResourceType)
   172  						c.Assert(len(pageResources), qt.Equals, 2)
   173  						firstPage := pageResources[0].(page.Page)
   174  						secondPage := pageResources[1].(page.Page)
   175  
   176  						c.Assert(firstPage.File().Filename(), qt.Equals, filepath.FromSlash("/work/base/b/my-bundle/1.md"))
   177  						c.Assert(content(firstPage), qt.Contains, "TheContent")
   178  						c.Assert(len(leafBundle1.Resources()), qt.Equals, 6)
   179  
   180  						// Verify shortcode in bundled page
   181  						c.Assert(content(secondPage), qt.Contains, filepath.FromSlash("MyShort in b/my-bundle/2.md"))
   182  
   183  						// https://github.com/gohugoio/hugo/issues/4582
   184  						c.Assert(firstPage.Parent(), qt.Equals, leafBundle1)
   185  						c.Assert(secondPage.Parent(), qt.Equals, leafBundle1)
   186  
   187  						c.Assert(pageResources.GetMatch("1*"), qt.Equals, firstPage)
   188  						c.Assert(pageResources.GetMatch("2*"), qt.Equals, secondPage)
   189  						c.Assert(pageResources.GetMatch("doesnotexist*"), qt.IsNil)
   190  
   191  						imageResources := leafBundle1.Resources().ByType("image")
   192  						c.Assert(len(imageResources), qt.Equals, 3)
   193  
   194  						c.Assert(leafBundle1.OutputFormats().Get("CUSTOMO"), qt.Not(qt.IsNil))
   195  
   196  						relPermalinker := func(s string) string {
   197  							return fmt.Sprintf(s, relURLBase)
   198  						}
   199  
   200  						permalinker := func(s string) string {
   201  							return fmt.Sprintf(s, baseURL)
   202  						}
   203  
   204  						if ugly {
   205  							b.AssertFileContent("/work/public/2017/pageslug.html",
   206  								relPermalinker("Single RelPermalink: %s/2017/pageslug.html"),
   207  								permalinker("Single Permalink: %s/2017/pageslug.html"),
   208  								relPermalinker("Sunset RelPermalink: %s/2017/pageslug/sunset1.jpg"),
   209  								permalinker("Sunset Permalink: %s/2017/pageslug/sunset1.jpg"))
   210  						} else {
   211  							b.AssertFileContent("/work/public/2017/pageslug/index.html",
   212  								relPermalinker("Sunset RelPermalink: %s/2017/pageslug/sunset1.jpg"),
   213  								permalinker("Sunset Permalink: %s/2017/pageslug/sunset1.jpg"))
   214  
   215  							b.AssertFileContent("/work/public/cpath/2017/pageslug/cindex.html",
   216  								relPermalinker("Single RelPermalink: %s/cpath/2017/pageslug/"),
   217  								relPermalinker("Short Sunset RelPermalink: %s/cpath/2017/pageslug/sunset2.jpg"),
   218  								relPermalinker("Sunset RelPermalink: %s/cpath/2017/pageslug/sunset1.jpg"),
   219  								permalinker("Sunset Permalink: %s/cpath/2017/pageslug/sunset1.jpg"),
   220  							)
   221  						}
   222  
   223  						b.AssertFileContent(filepath.FromSlash("/work/public/2017/pageslug/c/logo.png"), "content")
   224  						b.AssertFileContent(filepath.FromSlash("/work/public/cpath/2017/pageslug/c/logo.png"), "content")
   225  						c.Assert(b.CheckExists("/work/public/cpath/cpath/2017/pageslug/c/logo.png"), qt.Equals, false)
   226  
   227  						// Custom media type defined in site config.
   228  						c.Assert(len(leafBundle1.Resources().ByType("bepsays")), qt.Equals, 1)
   229  
   230  						if ugly {
   231  							b.AssertFileContent(filepath.FromSlash("/work/public/2017/pageslug.html"),
   232  								"TheContent",
   233  								relPermalinker("Sunset RelPermalink: %s/2017/pageslug/sunset1.jpg"),
   234  								permalinker("Sunset Permalink: %s/2017/pageslug/sunset1.jpg"),
   235  								"Thumb Width: 123",
   236  								"Thumb Name: my-sunset-1",
   237  								relPermalinker("Short Sunset RelPermalink: %s/2017/pageslug/sunset2.jpg"),
   238  								"Short Thumb Width: 56",
   239  								"1: Image Title: Sunset Galore 1",
   240  								"1: Image Params: map[myparam:My Sunny Param]",
   241  								relPermalinker("1: Image RelPermalink: %s/2017/pageslug/sunset1.jpg"),
   242  								"2: Image Title: Sunset Galore 2",
   243  								"2: Image Params: map[myparam:My Sunny Param]",
   244  								"1: Image myParam: Lower: My Sunny Param Caps: My Sunny Param",
   245  								"0: Page Title: Bundle Galore",
   246  							)
   247  
   248  							// https://github.com/gohugoio/hugo/issues/5882
   249  							b.AssertFileContent(
   250  								filepath.FromSlash("/work/public/2017/pageslug.html"), "0: Page RelPermalink: |")
   251  
   252  							b.AssertFileContent(filepath.FromSlash("/work/public/cpath/2017/pageslug.html"), "TheContent")
   253  
   254  							// 은행
   255  							b.AssertFileContent(filepath.FromSlash("/work/public/c/은행/logo-은행.png"), "은행 PNG")
   256  
   257  						} else {
   258  							b.AssertFileContent(filepath.FromSlash("/work/public/2017/pageslug/index.html"), "TheContent")
   259  							b.AssertFileContent(filepath.FromSlash("/work/public/cpath/2017/pageslug/cindex.html"), "TheContent")
   260  							b.AssertFileContent(filepath.FromSlash("/work/public/2017/pageslug/index.html"), "Single Title")
   261  							b.AssertFileContent(filepath.FromSlash("/work/public/root/index.html"), "Single Title")
   262  
   263  						}
   264  					})
   265  			}
   266  		}
   267  	}
   268  }
   269  
   270  func TestPageBundlerSiteMultilingual(t *testing.T) {
   271  	t.Parallel()
   272  
   273  	for _, ugly := range []bool{false, true} {
   274  		ugly := ugly
   275  		t.Run(fmt.Sprintf("ugly=%t", ugly),
   276  			func(t *testing.T) {
   277  				t.Parallel()
   278  				c := qt.New(t)
   279  				fs, cfg := newTestBundleSourcesMultilingual(t)
   280  				cfg.Set("uglyURLs", ugly)
   281  
   282  				b := newTestSitesBuilderFromDepsCfg(t, deps.DepsCfg{Fs: fs, Cfg: cfg}).WithNothingAdded()
   283  				b.Build(BuildCfg{})
   284  
   285  				sites := b.H
   286  
   287  				c.Assert(len(sites.Sites), qt.Equals, 2)
   288  
   289  				s := sites.Sites[0]
   290  
   291  				c.Assert(len(s.RegularPages()), qt.Equals, 8)
   292  				c.Assert(len(s.Pages()), qt.Equals, 16)
   293  				//dumpPages(s.AllPages()...)
   294  
   295  				c.Assert(len(s.AllPages()), qt.Equals, 31)
   296  
   297  				bundleWithSubPath := s.getPage(page.KindPage, "lb/index")
   298  				c.Assert(bundleWithSubPath, qt.Not(qt.IsNil))
   299  
   300  				// See https://github.com/gohugoio/hugo/issues/4312
   301  				// Before that issue:
   302  				// A bundle in a/b/index.en.md
   303  				// a/b/index.en.md => OK
   304  				// a/b/index => OK
   305  				// index.en.md => ambiguous, but OK.
   306  				// With bundles, the file name has little meaning, the folder it lives in does. So this should also work:
   307  				// a/b
   308  				// and probably also just b (aka "my-bundle")
   309  				// These may also be translated, so we also need to test that.
   310  				//  "bf", "my-bf-bundle", "index.md + nn
   311  				bfBundle := s.getPage(page.KindPage, "bf/my-bf-bundle/index")
   312  				c.Assert(bfBundle, qt.Not(qt.IsNil))
   313  				c.Assert(bfBundle.Language().Lang, qt.Equals, "en")
   314  				c.Assert(s.getPage(page.KindPage, "bf/my-bf-bundle/index.md"), qt.Equals, bfBundle)
   315  				c.Assert(s.getPage(page.KindPage, "bf/my-bf-bundle"), qt.Equals, bfBundle)
   316  				c.Assert(s.getPage(page.KindPage, "my-bf-bundle"), qt.Equals, bfBundle)
   317  
   318  				nnSite := sites.Sites[1]
   319  				c.Assert(len(nnSite.RegularPages()), qt.Equals, 7)
   320  
   321  				bfBundleNN := nnSite.getPage(page.KindPage, "bf/my-bf-bundle/index")
   322  				c.Assert(bfBundleNN, qt.Not(qt.IsNil))
   323  				c.Assert(bfBundleNN.Language().Lang, qt.Equals, "nn")
   324  				c.Assert(nnSite.getPage(page.KindPage, "bf/my-bf-bundle/index.nn.md"), qt.Equals, bfBundleNN)
   325  				c.Assert(nnSite.getPage(page.KindPage, "bf/my-bf-bundle"), qt.Equals, bfBundleNN)
   326  				c.Assert(nnSite.getPage(page.KindPage, "my-bf-bundle"), qt.Equals, bfBundleNN)
   327  
   328  				// See https://github.com/gohugoio/hugo/issues/4295
   329  				// Every resource should have its Name prefixed with its base folder.
   330  				cBundleResources := bundleWithSubPath.Resources().Match("c/**")
   331  				c.Assert(len(cBundleResources), qt.Equals, 4)
   332  				bundlePage := bundleWithSubPath.Resources().GetMatch("c/page*")
   333  				c.Assert(bundlePage, qt.Not(qt.IsNil))
   334  
   335  				bcBundleNN, _ := nnSite.getPageNew(nil, "bc")
   336  				c.Assert(bcBundleNN, qt.Not(qt.IsNil))
   337  				bcBundleEN, _ := s.getPageNew(nil, "bc")
   338  				c.Assert(bcBundleNN.Language().Lang, qt.Equals, "nn")
   339  				c.Assert(bcBundleEN.Language().Lang, qt.Equals, "en")
   340  				c.Assert(len(bcBundleNN.Resources()), qt.Equals, 3)
   341  				c.Assert(len(bcBundleEN.Resources()), qt.Equals, 3)
   342  				b.AssertFileContent("public/en/bc/data1.json", "data1")
   343  				b.AssertFileContent("public/en/bc/data2.json", "data2")
   344  				b.AssertFileContent("public/en/bc/logo-bc.png", "logo")
   345  				b.AssertFileContent("public/nn/bc/data1.nn.json", "data1.nn")
   346  				b.AssertFileContent("public/nn/bc/data2.json", "data2")
   347  				b.AssertFileContent("public/nn/bc/logo-bc.png", "logo")
   348  			})
   349  	}
   350  }
   351  
   352  func TestMultilingualDisableDefaultLanguage(t *testing.T) {
   353  	t.Parallel()
   354  
   355  	c := qt.New(t)
   356  	_, cfg := newTestBundleSourcesMultilingual(t)
   357  	cfg.Set("disableLanguages", []string{"en"})
   358  	l := configLoader{cfg: cfg}
   359  	err := l.applyConfigDefaults()
   360  	c.Assert(err, qt.IsNil)
   361  	err = l.loadLanguageSettings(nil)
   362  	c.Assert(err, qt.Not(qt.IsNil))
   363  	c.Assert(err.Error(), qt.Contains, "cannot disable default language")
   364  }
   365  
   366  func TestMultilingualDisableLanguage(t *testing.T) {
   367  	t.Parallel()
   368  
   369  	c := qt.New(t)
   370  	fs, cfg := newTestBundleSourcesMultilingual(t)
   371  	cfg.Set("disableLanguages", []string{"nn"})
   372  
   373  	b := newTestSitesBuilderFromDepsCfg(t, deps.DepsCfg{Fs: fs, Cfg: cfg}).WithNothingAdded()
   374  	b.Build(BuildCfg{})
   375  	sites := b.H
   376  
   377  	c.Assert(len(sites.Sites), qt.Equals, 1)
   378  
   379  	s := sites.Sites[0]
   380  
   381  	c.Assert(len(s.RegularPages()), qt.Equals, 8)
   382  	c.Assert(len(s.Pages()), qt.Equals, 16)
   383  	// No nn pages
   384  	c.Assert(len(s.AllPages()), qt.Equals, 16)
   385  	s.pageMap.withEveryBundlePage(func(p *pageState) bool {
   386  		c.Assert(p.Language().Lang != "nn", qt.Equals, true)
   387  		return false
   388  	})
   389  }
   390  
   391  func TestPageBundlerSiteWitSymbolicLinksInContent(t *testing.T) {
   392  	skipSymlink(t)
   393  
   394  	wd, _ := os.Getwd()
   395  	defer func() {
   396  		os.Chdir(wd)
   397  	}()
   398  
   399  	c := qt.New(t)
   400  	// We need to use the OS fs for this.
   401  	cfg := config.New()
   402  	fs := hugofs.NewFrom(hugofs.Os, cfg)
   403  
   404  	workDir, clean, err := htesting.CreateTempDir(hugofs.Os, "hugosym")
   405  	c.Assert(err, qt.IsNil)
   406  
   407  	contentDirName := "content"
   408  
   409  	contentDir := filepath.Join(workDir, contentDirName)
   410  	c.Assert(os.MkdirAll(filepath.Join(contentDir, "a"), 0777), qt.IsNil)
   411  
   412  	for i := 1; i <= 3; i++ {
   413  		c.Assert(os.MkdirAll(filepath.Join(workDir, fmt.Sprintf("symcontent%d", i)), 0777), qt.IsNil)
   414  	}
   415  
   416  	c.Assert(os.MkdirAll(filepath.Join(workDir, "symcontent2", "a1"), 0777), qt.IsNil)
   417  
   418  	// Symlinked sections inside content.
   419  	os.Chdir(contentDir)
   420  	for i := 1; i <= 3; i++ {
   421  		c.Assert(os.Symlink(filepath.FromSlash(fmt.Sprintf(("../symcontent%d"), i)), fmt.Sprintf("symbolic%d", i)), qt.IsNil)
   422  	}
   423  
   424  	c.Assert(os.Chdir(filepath.Join(contentDir, "a")), qt.IsNil)
   425  
   426  	// Create a symlink to one single content file
   427  	c.Assert(os.Symlink(filepath.FromSlash("../../symcontent2/a1/page.md"), "page_s.md"), qt.IsNil)
   428  
   429  	c.Assert(os.Chdir(filepath.FromSlash("../../symcontent3")), qt.IsNil)
   430  
   431  	// Create a circular symlink. Will print some warnings.
   432  	c.Assert(os.Symlink(filepath.Join("..", contentDirName), filepath.FromSlash("circus")), qt.IsNil)
   433  
   434  	c.Assert(os.Chdir(workDir), qt.IsNil)
   435  
   436  	defer clean()
   437  
   438  	cfg.Set("workingDir", workDir)
   439  	cfg.Set("contentDir", contentDirName)
   440  	cfg.Set("baseURL", "https://example.com")
   441  
   442  	layout := `{{ .Title }}|{{ .Content }}`
   443  	pageContent := `---
   444  slug: %s
   445  date: 2017-10-09
   446  ---
   447  
   448  TheContent.
   449  `
   450  
   451  	b := newTestSitesBuilderFromDepsCfg(t, deps.DepsCfg{
   452  		Fs:  fs,
   453  		Cfg: cfg,
   454  	})
   455  
   456  	b.WithTemplates(
   457  		"_default/single.html", layout,
   458  		"_default/list.html", layout,
   459  	)
   460  
   461  	b.WithContent(
   462  		"a/regular.md", fmt.Sprintf(pageContent, "a1"),
   463  	)
   464  
   465  	b.WithSourceFile(
   466  		"symcontent1/s1.md", fmt.Sprintf(pageContent, "s1"),
   467  		"symcontent1/s2.md", fmt.Sprintf(pageContent, "s2"),
   468  		// Regular files inside symlinked folder.
   469  		"symcontent1/s1.md", fmt.Sprintf(pageContent, "s1"),
   470  		"symcontent1/s2.md", fmt.Sprintf(pageContent, "s2"),
   471  
   472  		// A bundle
   473  		"symcontent2/a1/index.md", fmt.Sprintf(pageContent, ""),
   474  		"symcontent2/a1/page.md", fmt.Sprintf(pageContent, "page"),
   475  		"symcontent2/a1/logo.png", "image",
   476  
   477  		// Assets
   478  		"symcontent3/s1.png", "image",
   479  		"symcontent3/s2.png", "image",
   480  	)
   481  
   482  	b.Build(BuildCfg{})
   483  	s := b.H.Sites[0]
   484  
   485  	c.Assert(len(s.RegularPages()), qt.Equals, 7)
   486  	a1Bundle := s.getPage(page.KindPage, "symbolic2/a1/index.md")
   487  	c.Assert(a1Bundle, qt.Not(qt.IsNil))
   488  	c.Assert(len(a1Bundle.Resources()), qt.Equals, 2)
   489  	c.Assert(len(a1Bundle.Resources().ByType(pageResourceType)), qt.Equals, 1)
   490  
   491  	b.AssertFileContent(filepath.FromSlash(workDir+"/public/a/page/index.html"), "TheContent")
   492  	b.AssertFileContent(filepath.FromSlash(workDir+"/public/symbolic1/s1/index.html"), "TheContent")
   493  	b.AssertFileContent(filepath.FromSlash(workDir+"/public/symbolic2/a1/index.html"), "TheContent")
   494  }
   495  
   496  func TestPageBundlerHeadless(t *testing.T) {
   497  	t.Parallel()
   498  
   499  	cfg, fs := newTestCfg()
   500  	c := qt.New(t)
   501  
   502  	workDir := "/work"
   503  	cfg.Set("workingDir", workDir)
   504  	cfg.Set("contentDir", "base")
   505  	cfg.Set("baseURL", "https://example.com")
   506  
   507  	pageContent := `---
   508  title: "Bundle Galore"
   509  slug: s1
   510  date: 2017-01-23
   511  ---
   512  
   513  TheContent.
   514  
   515  {{< myShort >}}
   516  `
   517  
   518  	writeSource(t, fs, filepath.Join(workDir, "layouts", "_default", "single.html"), "single {{ .Content }}")
   519  	writeSource(t, fs, filepath.Join(workDir, "layouts", "_default", "list.html"), "list")
   520  	writeSource(t, fs, filepath.Join(workDir, "layouts", "shortcodes", "myShort.html"), "SHORTCODE")
   521  
   522  	writeSource(t, fs, filepath.Join(workDir, "base", "a", "index.md"), pageContent)
   523  	writeSource(t, fs, filepath.Join(workDir, "base", "a", "l1.png"), "PNG image")
   524  	writeSource(t, fs, filepath.Join(workDir, "base", "a", "l2.png"), "PNG image")
   525  
   526  	writeSource(t, fs, filepath.Join(workDir, "base", "b", "index.md"), `---
   527  title: "Headless Bundle in Topless Bar"
   528  slug: s2
   529  headless: true
   530  date: 2017-01-23
   531  ---
   532  
   533  TheContent.
   534  HEADLESS {{< myShort >}}
   535  `)
   536  	writeSource(t, fs, filepath.Join(workDir, "base", "b", "l1.png"), "PNG image")
   537  	writeSource(t, fs, filepath.Join(workDir, "base", "b", "l2.png"), "PNG image")
   538  	writeSource(t, fs, filepath.Join(workDir, "base", "b", "p1.md"), pageContent)
   539  
   540  	s := buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{})
   541  
   542  	c.Assert(len(s.RegularPages()), qt.Equals, 1)
   543  
   544  	regular := s.getPage(page.KindPage, "a/index")
   545  	c.Assert(regular.RelPermalink(), qt.Equals, "/s1/")
   546  
   547  	headless := s.getPage(page.KindPage, "b/index")
   548  	c.Assert(headless, qt.Not(qt.IsNil))
   549  	c.Assert(headless.Title(), qt.Equals, "Headless Bundle in Topless Bar")
   550  	c.Assert(headless.RelPermalink(), qt.Equals, "")
   551  	c.Assert(headless.Permalink(), qt.Equals, "")
   552  	c.Assert(content(headless), qt.Contains, "HEADLESS SHORTCODE")
   553  
   554  	headlessResources := headless.Resources()
   555  	c.Assert(len(headlessResources), qt.Equals, 3)
   556  	c.Assert(len(headlessResources.Match("l*")), qt.Equals, 2)
   557  	pageResource := headlessResources.GetMatch("p*")
   558  	c.Assert(pageResource, qt.Not(qt.IsNil))
   559  	p := pageResource.(page.Page)
   560  	c.Assert(content(p), qt.Contains, "SHORTCODE")
   561  	c.Assert(p.Name(), qt.Equals, "p1.md")
   562  
   563  	th := newTestHelper(s.Cfg, s.Fs, t)
   564  
   565  	th.assertFileContent(filepath.FromSlash(workDir+"/public/s1/index.html"), "TheContent")
   566  	th.assertFileContent(filepath.FromSlash(workDir+"/public/s1/l1.png"), "PNG")
   567  
   568  	th.assertFileNotExist(workDir + "/public/s2/index.html")
   569  	// But the bundled resources needs to be published
   570  	th.assertFileContent(filepath.FromSlash(workDir+"/public/s2/l1.png"), "PNG")
   571  
   572  	// No headless bundles here, please.
   573  	// https://github.com/gohugoio/hugo/issues/6492
   574  	c.Assert(s.RegularPages(), qt.HasLen, 1)
   575  	c.Assert(s.home.RegularPages(), qt.HasLen, 1)
   576  	c.Assert(s.home.Pages(), qt.HasLen, 1)
   577  }
   578  
   579  func TestPageBundlerHeadlessIssue6552(t *testing.T) {
   580  	t.Parallel()
   581  
   582  	b := newTestSitesBuilder(t)
   583  	b.WithContent("headless/h1/index.md", `
   584  ---
   585  title: My Headless Bundle1
   586  headless: true
   587  ---
   588  `, "headless/h1/p1.md", `
   589  ---
   590  title: P1
   591  ---
   592  `, "headless/h2/index.md", `
   593  ---
   594  title: My Headless Bundle2
   595  headless: true
   596  ---
   597  `)
   598  
   599  	b.WithTemplatesAdded("index.html", `
   600  {{ $headless1 := .Site.GetPage "headless/h1" }}
   601  {{ $headless2 := .Site.GetPage "headless/h2" }}
   602  
   603  HEADLESS1: {{ $headless1.Title }}|{{ $headless1.RelPermalink }}|{{ len $headless1.Resources }}|
   604  HEADLESS2: {{ $headless2.Title }}{{ $headless2.RelPermalink }}|{{ len $headless2.Resources }}|
   605  
   606  `)
   607  
   608  	b.Build(BuildCfg{})
   609  
   610  	b.AssertFileContent("public/index.html", `
   611  HEADLESS1: My Headless Bundle1||1|
   612  HEADLESS2: My Headless Bundle2|0|
   613  `)
   614  }
   615  
   616  func TestMultiSiteBundles(t *testing.T) {
   617  	c := qt.New(t)
   618  	b := newTestSitesBuilder(t)
   619  	b.WithConfigFile("toml", `
   620  
   621  baseURL = "http://example.com/"
   622  
   623  defaultContentLanguage = "en"
   624  
   625  [languages]
   626  [languages.en]
   627  weight = 10
   628  contentDir = "content/en"
   629  [languages.nn]
   630  weight = 20
   631  contentDir = "content/nn"
   632  
   633  
   634  `)
   635  
   636  	b.WithContent("en/mybundle/index.md", `
   637  ---
   638  headless: true
   639  ---
   640  
   641  `)
   642  
   643  	b.WithContent("nn/mybundle/index.md", `
   644  ---
   645  headless: true
   646  ---
   647  
   648  `)
   649  
   650  	b.WithContent("en/mybundle/data.yaml", `data en`)
   651  	b.WithContent("en/mybundle/forms.yaml", `forms en`)
   652  	b.WithContent("nn/mybundle/data.yaml", `data nn`)
   653  
   654  	b.WithContent("en/_index.md", `
   655  ---
   656  Title: Home
   657  ---
   658  
   659  Home content.
   660  
   661  `)
   662  
   663  	b.WithContent("en/section-not-bundle/_index.md", `
   664  ---
   665  Title: Section Page
   666  ---
   667  
   668  Section content.
   669  
   670  `)
   671  
   672  	b.WithContent("en/section-not-bundle/single.md", `
   673  ---
   674  Title: Section Single
   675  Date: 2018-02-01
   676  ---
   677  
   678  Single content.
   679  
   680  `)
   681  
   682  	b.Build(BuildCfg{})
   683  
   684  	b.AssertFileContent("public/nn/mybundle/data.yaml", "data nn")
   685  	b.AssertFileContent("public/nn/mybundle/forms.yaml", "forms en")
   686  	b.AssertFileContent("public/mybundle/data.yaml", "data en")
   687  	b.AssertFileContent("public/mybundle/forms.yaml", "forms en")
   688  
   689  	c.Assert(b.CheckExists("public/nn/nn/mybundle/data.yaml"), qt.Equals, false)
   690  	c.Assert(b.CheckExists("public/en/mybundle/data.yaml"), qt.Equals, false)
   691  
   692  	homeEn := b.H.Sites[0].home
   693  	c.Assert(homeEn, qt.Not(qt.IsNil))
   694  	c.Assert(homeEn.Date().Year(), qt.Equals, 2018)
   695  
   696  	b.AssertFileContent("public/section-not-bundle/index.html", "Section Page", "Content: <p>Section content.</p>")
   697  	b.AssertFileContent("public/section-not-bundle/single/index.html", "Section Single", "|<p>Single content.</p>")
   698  }
   699  
   700  func newTestBundleSources(t testing.TB) (*hugofs.Fs, config.Provider) {
   701  	cfg, fs := newTestCfgBasic()
   702  	c := qt.New(t)
   703  
   704  	workDir := "/work"
   705  	cfg.Set("workingDir", workDir)
   706  	cfg.Set("contentDir", "base")
   707  	cfg.Set("baseURL", "https://example.com")
   708  	cfg.Set("mediaTypes", map[string]interface{}{
   709  		"bepsays/bep": map[string]interface{}{
   710  			"suffixes": []string{"bep"},
   711  		},
   712  	})
   713  
   714  	pageContent := `---
   715  title: "Bundle Galore"
   716  slug: pageslug
   717  date: 2017-10-09
   718  ---
   719  
   720  TheContent.
   721  `
   722  
   723  	pageContentShortcode := `---
   724  title: "Bundle Galore"
   725  slug: pageslug
   726  date: 2017-10-09
   727  ---
   728  
   729  TheContent.
   730  
   731  {{< myShort >}}
   732  `
   733  
   734  	pageWithImageShortcodeAndResourceMetadataContent := `---
   735  title: "Bundle Galore"
   736  slug: pageslug
   737  date: 2017-10-09
   738  resources:
   739  - src: "*.jpg"
   740    name: "my-sunset-:counter"
   741    title: "Sunset Galore :counter"
   742    params:
   743      myParam: "My Sunny Param"
   744  ---
   745  
   746  TheContent.
   747  
   748  {{< myShort >}}
   749  `
   750  
   751  	pageContentNoSlug := `---
   752  title: "Bundle Galore #2"
   753  date: 2017-10-09
   754  ---
   755  
   756  TheContent.
   757  `
   758  
   759  	singleLayout := `
   760  Single Title: {{ .Title }}
   761  Single RelPermalink: {{ .RelPermalink }}
   762  Single Permalink: {{ .Permalink }}
   763  Content: {{ .Content }}
   764  {{ $sunset := .Resources.GetMatch "my-sunset-1*" }}
   765  {{ with $sunset }}
   766  Sunset RelPermalink: {{ .RelPermalink }}
   767  Sunset Permalink: {{ .Permalink }}
   768  {{ $thumb := .Fill "123x123" }}
   769  Thumb Width: {{ $thumb.Width }}
   770  Thumb Name: {{ $thumb.Name }}
   771  Thumb Title: {{ $thumb.Title }}
   772  Thumb RelPermalink: {{ $thumb.RelPermalink }}
   773  {{ end }}
   774  {{ $types := slice "image" "page" }}
   775  {{ range $types }}
   776  {{ $typeTitle := . | title }}
   777  {{ range $i, $e := $.Resources.ByType . }}
   778  {{ $i }}: {{ $typeTitle }} Title: {{ .Title }}
   779  {{ $i }}: {{ $typeTitle }} Name: {{ .Name }}
   780  {{ $i }}: {{ $typeTitle }} RelPermalink: {{ .RelPermalink }}|
   781  {{ $i }}: {{ $typeTitle }} Params: {{ printf "%v" .Params }}
   782  {{ $i }}: {{ $typeTitle }} myParam: Lower: {{ .Params.myparam }} Caps: {{ .Params.MYPARAM }}
   783  {{ end }}
   784  {{ end }}
   785  `
   786  
   787  	myShort := `
   788  MyShort in {{ .Page.File.Path }}:
   789  {{ $sunset := .Page.Resources.GetMatch "my-sunset-2*" }}
   790  {{ with $sunset }}
   791  Short Sunset RelPermalink: {{ .RelPermalink }}
   792  {{ $thumb := .Fill "56x56" }}
   793  Short Thumb Width: {{ $thumb.Width }}
   794  {{ end }}
   795  `
   796  
   797  	listLayout := `{{ .Title }}|{{ .Content }}`
   798  
   799  	writeSource(t, fs, filepath.Join(workDir, "layouts", "_default", "single.html"), singleLayout)
   800  	writeSource(t, fs, filepath.Join(workDir, "layouts", "_default", "list.html"), listLayout)
   801  	writeSource(t, fs, filepath.Join(workDir, "layouts", "shortcodes", "myShort.html"), myShort)
   802  	writeSource(t, fs, filepath.Join(workDir, "layouts", "shortcodes", "myShort.customo"), myShort)
   803  
   804  	writeSource(t, fs, filepath.Join(workDir, "base", "_index.md"), pageContent)
   805  	writeSource(t, fs, filepath.Join(workDir, "base", "_1.md"), pageContent)
   806  	writeSource(t, fs, filepath.Join(workDir, "base", "_1.png"), pageContent)
   807  
   808  	writeSource(t, fs, filepath.Join(workDir, "base", "images", "hugo-logo.png"), "content")
   809  	writeSource(t, fs, filepath.Join(workDir, "base", "a", "2.md"), pageContent)
   810  	writeSource(t, fs, filepath.Join(workDir, "base", "a", "1.md"), pageContent)
   811  
   812  	writeSource(t, fs, filepath.Join(workDir, "base", "a", "b", "index.md"), pageContentNoSlug)
   813  	writeSource(t, fs, filepath.Join(workDir, "base", "a", "b", "ab1.md"), pageContentNoSlug)
   814  
   815  	// Mostly plain static assets in a folder with a page in a sub folder thrown in.
   816  	writeSource(t, fs, filepath.Join(workDir, "base", "assets", "pic1.png"), "content")
   817  	writeSource(t, fs, filepath.Join(workDir, "base", "assets", "pic2.png"), "content")
   818  	writeSource(t, fs, filepath.Join(workDir, "base", "assets", "pages", "mypage.md"), pageContent)
   819  
   820  	// Bundle
   821  	writeSource(t, fs, filepath.Join(workDir, "base", "b", "my-bundle", "index.md"), pageWithImageShortcodeAndResourceMetadataContent)
   822  	writeSource(t, fs, filepath.Join(workDir, "base", "b", "my-bundle", "1.md"), pageContent)
   823  	writeSource(t, fs, filepath.Join(workDir, "base", "b", "my-bundle", "2.md"), pageContentShortcode)
   824  	writeSource(t, fs, filepath.Join(workDir, "base", "b", "my-bundle", "custom-mime.bep"), "bepsays")
   825  	writeSource(t, fs, filepath.Join(workDir, "base", "b", "my-bundle", "c", "logo.png"), "content")
   826  
   827  	// Bundle with 은행 slug
   828  	// See https://github.com/gohugoio/hugo/issues/4241
   829  	writeSource(t, fs, filepath.Join(workDir, "base", "c", "bundle", "index.md"), `---
   830  title: "은행 은행"
   831  slug: 은행
   832  date: 2017-10-09
   833  ---
   834  
   835  Content for 은행.
   836  `)
   837  
   838  	// Bundle in root
   839  	writeSource(t, fs, filepath.Join(workDir, "base", "root", "index.md"), pageWithImageShortcodeAndResourceMetadataContent)
   840  	writeSource(t, fs, filepath.Join(workDir, "base", "root", "1.md"), pageContent)
   841  	writeSource(t, fs, filepath.Join(workDir, "base", "root", "c", "logo.png"), "content")
   842  
   843  	writeSource(t, fs, filepath.Join(workDir, "base", "c", "bundle", "logo-은행.png"), "은행 PNG")
   844  
   845  	// Write a real image into one of the bundle above.
   846  	src, err := os.Open("testdata/sunset.jpg")
   847  	c.Assert(err, qt.IsNil)
   848  
   849  	// We need 2 to test https://github.com/gohugoio/hugo/issues/4202
   850  	out, err := fs.Source.Create(filepath.Join(workDir, "base", "b", "my-bundle", "sunset1.jpg"))
   851  	c.Assert(err, qt.IsNil)
   852  	out2, err := fs.Source.Create(filepath.Join(workDir, "base", "b", "my-bundle", "sunset2.jpg"))
   853  	c.Assert(err, qt.IsNil)
   854  
   855  	_, err = io.Copy(out, src)
   856  	c.Assert(err, qt.IsNil)
   857  	out.Close()
   858  	src.Seek(0, 0)
   859  	_, err = io.Copy(out2, src)
   860  	out2.Close()
   861  	src.Close()
   862  	c.Assert(err, qt.IsNil)
   863  
   864  	return fs, cfg
   865  }
   866  
   867  func newTestBundleSourcesMultilingual(t *testing.T) (*hugofs.Fs, config.Provider) {
   868  	cfg, fs := newTestCfgBasic()
   869  
   870  	workDir := "/work"
   871  	cfg.Set("workingDir", workDir)
   872  	cfg.Set("contentDir", "base")
   873  	cfg.Set("baseURL", "https://example.com")
   874  	cfg.Set("defaultContentLanguage", "en")
   875  
   876  	langConfig := map[string]interface{}{
   877  		"en": map[string]interface{}{
   878  			"weight":       1,
   879  			"languageName": "English",
   880  		},
   881  		"nn": map[string]interface{}{
   882  			"weight":       2,
   883  			"languageName": "Nynorsk",
   884  		},
   885  	}
   886  
   887  	cfg.Set("languages", langConfig)
   888  
   889  	pageContent := `---
   890  slug: pageslug
   891  date: 2017-10-09
   892  ---
   893  
   894  TheContent.
   895  `
   896  
   897  	layout := `{{ .Title }}|{{ .Content }}|Lang: {{ .Site.Language.Lang }}`
   898  
   899  	writeSource(t, fs, filepath.Join(workDir, "layouts", "_default", "single.html"), layout)
   900  	writeSource(t, fs, filepath.Join(workDir, "layouts", "_default", "list.html"), layout)
   901  
   902  	writeSource(t, fs, filepath.Join(workDir, "base", "1s", "mypage.md"), pageContent)
   903  	writeSource(t, fs, filepath.Join(workDir, "base", "1s", "mypage.nn.md"), pageContent)
   904  	writeSource(t, fs, filepath.Join(workDir, "base", "1s", "mylogo.png"), "content")
   905  
   906  	writeSource(t, fs, filepath.Join(workDir, "base", "bb", "_index.md"), pageContent)
   907  	writeSource(t, fs, filepath.Join(workDir, "base", "bb", "_index.nn.md"), pageContent)
   908  	writeSource(t, fs, filepath.Join(workDir, "base", "bb", "en.md"), pageContent)
   909  	writeSource(t, fs, filepath.Join(workDir, "base", "bb", "_1.md"), pageContent)
   910  	writeSource(t, fs, filepath.Join(workDir, "base", "bb", "_1.nn.md"), pageContent)
   911  	writeSource(t, fs, filepath.Join(workDir, "base", "bb", "a.png"), "content")
   912  	writeSource(t, fs, filepath.Join(workDir, "base", "bb", "b.png"), "content")
   913  	writeSource(t, fs, filepath.Join(workDir, "base", "bb", "b.nn.png"), "content")
   914  	writeSource(t, fs, filepath.Join(workDir, "base", "bb", "c.nn.png"), "content")
   915  	writeSource(t, fs, filepath.Join(workDir, "base", "bb", "b", "d.nn.png"), "content")
   916  
   917  	writeSource(t, fs, filepath.Join(workDir, "base", "bc", "_index.md"), pageContent)
   918  	writeSource(t, fs, filepath.Join(workDir, "base", "bc", "_index.nn.md"), pageContent)
   919  	writeSource(t, fs, filepath.Join(workDir, "base", "bc", "page.md"), pageContent)
   920  	writeSource(t, fs, filepath.Join(workDir, "base", "bc", "logo-bc.png"), "logo")
   921  	writeSource(t, fs, filepath.Join(workDir, "base", "bc", "page.nn.md"), pageContent)
   922  	writeSource(t, fs, filepath.Join(workDir, "base", "bc", "data1.json"), "data1")
   923  	writeSource(t, fs, filepath.Join(workDir, "base", "bc", "data2.json"), "data2")
   924  	writeSource(t, fs, filepath.Join(workDir, "base", "bc", "data1.nn.json"), "data1.nn")
   925  
   926  	writeSource(t, fs, filepath.Join(workDir, "base", "bd", "index.md"), pageContent)
   927  	writeSource(t, fs, filepath.Join(workDir, "base", "bd", "page.md"), pageContent)
   928  	writeSource(t, fs, filepath.Join(workDir, "base", "bd", "page.nn.md"), pageContent)
   929  
   930  	writeSource(t, fs, filepath.Join(workDir, "base", "be", "_index.md"), pageContent)
   931  	writeSource(t, fs, filepath.Join(workDir, "base", "be", "page.md"), pageContent)
   932  	writeSource(t, fs, filepath.Join(workDir, "base", "be", "page.nn.md"), pageContent)
   933  
   934  	// Bundle leaf,  multilingual
   935  	writeSource(t, fs, filepath.Join(workDir, "base", "lb", "index.md"), pageContent)
   936  	writeSource(t, fs, filepath.Join(workDir, "base", "lb", "index.nn.md"), pageContent)
   937  	writeSource(t, fs, filepath.Join(workDir, "base", "lb", "1.md"), pageContent)
   938  	writeSource(t, fs, filepath.Join(workDir, "base", "lb", "2.md"), pageContent)
   939  	writeSource(t, fs, filepath.Join(workDir, "base", "lb", "2.nn.md"), pageContent)
   940  	writeSource(t, fs, filepath.Join(workDir, "base", "lb", "c", "page.md"), pageContent)
   941  	writeSource(t, fs, filepath.Join(workDir, "base", "lb", "c", "logo.png"), "content")
   942  	writeSource(t, fs, filepath.Join(workDir, "base", "lb", "c", "logo.nn.png"), "content")
   943  	writeSource(t, fs, filepath.Join(workDir, "base", "lb", "c", "one.png"), "content")
   944  	writeSource(t, fs, filepath.Join(workDir, "base", "lb", "c", "d", "deep.png"), "content")
   945  
   946  	// Translated bundle in some sensible sub path.
   947  	writeSource(t, fs, filepath.Join(workDir, "base", "bf", "my-bf-bundle", "index.md"), pageContent)
   948  	writeSource(t, fs, filepath.Join(workDir, "base", "bf", "my-bf-bundle", "index.nn.md"), pageContent)
   949  	writeSource(t, fs, filepath.Join(workDir, "base", "bf", "my-bf-bundle", "page.md"), pageContent)
   950  
   951  	return fs, cfg
   952  }
   953  
   954  // https://github.com/gohugoio/hugo/issues/5858
   955  func TestBundledResourcesWhenMultipleOutputFormats(t *testing.T) {
   956  	t.Parallel()
   957  
   958  	b := newTestSitesBuilder(t).Running().WithConfigFile("toml", `
   959  baseURL = "https://example.org"
   960  [outputs]
   961    # This looks odd, but it triggers the behaviour in #5858
   962    # The total output formats list gets sorted, so CSS before HTML.
   963    home = [ "CSS" ]
   964  
   965  `)
   966  	b.WithContent("mybundle/index.md", `
   967  ---
   968  title: Page
   969  date: 2017-01-15
   970  ---
   971  `,
   972  		"mybundle/data.json", "MyData",
   973  	)
   974  
   975  	b.CreateSites().Build(BuildCfg{})
   976  
   977  	b.AssertFileContent("public/mybundle/data.json", "MyData")
   978  
   979  	// Change the bundled JSON file and make sure it gets republished.
   980  	b.EditFiles("content/mybundle/data.json", "My changed data")
   981  
   982  	b.Build(BuildCfg{})
   983  
   984  	b.AssertFileContent("public/mybundle/data.json", "My changed data")
   985  }
   986  
   987  // https://github.com/gohugoio/hugo/issues/4870
   988  func TestBundleSlug(t *testing.T) {
   989  	t.Parallel()
   990  	c := qt.New(t)
   991  
   992  	const pageTemplate = `---
   993  title: Title
   994  slug: %s
   995  ---
   996  `
   997  
   998  	b := newTestSitesBuilder(t)
   999  
  1000  	b.WithTemplatesAdded("index.html", `{{ range .Site.RegularPages }}|{{ .RelPermalink }}{{ end }}|`)
  1001  	b.WithSimpleConfigFile().
  1002  		WithContent("about/services1/misc.md", fmt.Sprintf(pageTemplate, "this-is-the-slug")).
  1003  		WithContent("about/services2/misc/index.md", fmt.Sprintf(pageTemplate, "this-is-another-slug"))
  1004  
  1005  	b.CreateSites().Build(BuildCfg{})
  1006  
  1007  	b.AssertHome(
  1008  		"|/about/services1/this-is-the-slug/|/",
  1009  		"|/about/services2/this-is-another-slug/|")
  1010  
  1011  	c.Assert(b.CheckExists("public/about/services1/this-is-the-slug/index.html"), qt.Equals, true)
  1012  	c.Assert(b.CheckExists("public/about/services2/this-is-another-slug/index.html"), qt.Equals, true)
  1013  }
  1014  
  1015  func TestBundleMisc(t *testing.T) {
  1016  	config := `
  1017  baseURL = "https://example.com"
  1018  defaultContentLanguage = "en"
  1019  defaultContentLanguageInSubdir = true
  1020  ignoreFiles = ["README\\.md", "content/en/ignore"]
  1021  
  1022  [Languages]
  1023  [Languages.en]
  1024  weight = 99999
  1025  contentDir = "content/en"
  1026  [Languages.nn]
  1027  weight = 20
  1028  contentDir = "content/nn"
  1029  [Languages.sv]
  1030  weight = 30
  1031  contentDir = "content/sv"
  1032  [Languages.nb]
  1033  weight = 40
  1034  contentDir = "content/nb"
  1035  
  1036  `
  1037  
  1038  	const pageContent = `---
  1039  title: %q
  1040  ---
  1041  `
  1042  	createPage := func(s string) string {
  1043  		return fmt.Sprintf(pageContent, s)
  1044  	}
  1045  
  1046  	b := newTestSitesBuilder(t).WithConfigFile("toml", config)
  1047  	b.WithLogger(loggers.NewWarningLogger())
  1048  
  1049  	b.WithTemplates("_default/list.html", `{{ range .Site.Pages }}
  1050  {{ .Kind }}|{{ .Path }}|{{ with .CurrentSection }}CurrentSection: {{ .Path }}{{ end }}|{{ .RelPermalink }}{{ end }}
  1051  `)
  1052  
  1053  	b.WithTemplates("_default/single.html", `Single: {{ .Title }}`)
  1054  
  1055  	b.WithContent("en/sect1/sect2/_index.md", createPage("en: Sect 2"))
  1056  	b.WithContent("en/sect1/sect2/page.md", createPage("en: Page"))
  1057  	b.WithContent("en/sect1/sect2/data-branch.json", "mydata")
  1058  	b.WithContent("nn/sect1/sect2/page.md", createPage("nn: Page"))
  1059  	b.WithContent("nn/sect1/sect2/data-branch.json", "my nn data")
  1060  
  1061  	// En only
  1062  	b.WithContent("en/enonly/myen.md", createPage("en: Page"))
  1063  	b.WithContent("en/enonly/myendata.json", "mydata")
  1064  
  1065  	// Leaf
  1066  
  1067  	b.WithContent("nn/b1/index.md", createPage("nn: leaf"))
  1068  	b.WithContent("en/b1/index.md", createPage("en: leaf"))
  1069  	b.WithContent("sv/b1/index.md", createPage("sv: leaf"))
  1070  	b.WithContent("nb/b1/index.md", createPage("nb: leaf"))
  1071  
  1072  	// Should be ignored
  1073  	b.WithContent("en/ignore/page.md", createPage("en: ignore"))
  1074  	b.WithContent("en/README.md", createPage("en: ignore"))
  1075  
  1076  	// Both leaf and branch bundle in same dir
  1077  	b.WithContent("en/b2/index.md", `---
  1078  slug: leaf
  1079  ---
  1080  `)
  1081  	b.WithContent("en/b2/_index.md", createPage("en: branch"))
  1082  
  1083  	b.WithContent("en/b1/data1.json", "en: data")
  1084  	b.WithContent("sv/b1/data1.json", "sv: data")
  1085  	b.WithContent("sv/b1/data2.json", "sv: data2")
  1086  	b.WithContent("nb/b1/data2.json", "nb: data2")
  1087  
  1088  	b.WithContent("en/b3/_index.md", createPage("en: branch"))
  1089  	b.WithContent("en/b3/p1.md", createPage("en: page"))
  1090  	b.WithContent("en/b3/data1.json", "en: data")
  1091  
  1092  	b.Build(BuildCfg{})
  1093  
  1094  	b.AssertFileContent("public/en/index.html",
  1095  		filepath.FromSlash("section|sect1/sect2/_index.md|CurrentSection: sect1/sect2/_index.md"),
  1096  		"myen.md|CurrentSection: enonly")
  1097  
  1098  	b.AssertFileContentFn("public/en/index.html", func(s string) bool {
  1099  		// Check ignored files
  1100  		return !regexp.MustCompile("README|ignore").MatchString(s)
  1101  	})
  1102  
  1103  	b.AssertFileContent("public/nn/index.html", filepath.FromSlash("page|sect1/sect2/page.md|CurrentSection: sect1"))
  1104  	b.AssertFileContentFn("public/nn/index.html", func(s string) bool {
  1105  		return !strings.Contains(s, "enonly")
  1106  	})
  1107  
  1108  	// Check order of inherited data file
  1109  	b.AssertFileContent("public/nb/b1/data1.json", "en: data") // Default content
  1110  	b.AssertFileContent("public/nn/b1/data2.json", "sv: data") // First match
  1111  
  1112  	b.AssertFileContent("public/en/enonly/myen/index.html", "Single: en: Page")
  1113  	b.AssertFileContent("public/en/enonly/myendata.json", "mydata")
  1114  
  1115  	c := qt.New(t)
  1116  	c.Assert(b.CheckExists("public/sv/enonly/myen/index.html"), qt.Equals, false)
  1117  
  1118  	// Both leaf and branch bundle in same dir
  1119  	// We log a warning about it, but we keep both.
  1120  	b.AssertFileContent("public/en/b2/index.html",
  1121  		"/en/b2/leaf/",
  1122  		filepath.FromSlash("section|sect1/sect2/_index.md|CurrentSection: sect1/sect2/_index.md"))
  1123  }
  1124  
  1125  // Issue 6136
  1126  func TestPageBundlerPartialTranslations(t *testing.T) {
  1127  	config := `
  1128  baseURL = "https://example.org"
  1129  defaultContentLanguage = "en"
  1130  defaultContentLanguageInSubDir = true
  1131  disableKinds = ["taxonomy", "term"]
  1132  [languages]
  1133  [languages.nn]
  1134  languageName = "Nynorsk"
  1135  weight = 2
  1136  title = "Tittel på Nynorsk"
  1137  [languages.en]
  1138  title = "Title in English"
  1139  languageName = "English"
  1140  weight = 1
  1141  `
  1142  
  1143  	pageContent := func(id string) string {
  1144  		return fmt.Sprintf(`
  1145  ---
  1146  title: %q
  1147  ---
  1148  `, id)
  1149  	}
  1150  
  1151  	dataContent := func(id string) string {
  1152  		return id
  1153  	}
  1154  
  1155  	b := newTestSitesBuilder(t).WithConfigFile("toml", config)
  1156  
  1157  	b.WithContent("blog/sect1/_index.nn.md", pageContent("s1.nn"))
  1158  	b.WithContent("blog/sect1/data.json", dataContent("s1.data"))
  1159  
  1160  	b.WithContent("blog/sect1/b1/index.nn.md", pageContent("s1.b1.nn"))
  1161  	b.WithContent("blog/sect1/b1/data.json", dataContent("s1.b1.data"))
  1162  
  1163  	b.WithContent("blog/sect2/_index.md", pageContent("s2"))
  1164  	b.WithContent("blog/sect2/data.json", dataContent("s2.data"))
  1165  
  1166  	b.WithContent("blog/sect2/b1/index.md", pageContent("s2.b1"))
  1167  	b.WithContent("blog/sect2/b1/data.json", dataContent("s2.b1.data"))
  1168  
  1169  	b.WithContent("blog/sect2/b2/index.md", pageContent("s2.b2"))
  1170  	b.WithContent("blog/sect2/b2/bp.md", pageContent("s2.b2.bundlecontent"))
  1171  
  1172  	b.WithContent("blog/sect2/b3/index.md", pageContent("s2.b3"))
  1173  	b.WithContent("blog/sect2/b3/bp.nn.md", pageContent("s2.b3.bundlecontent.nn"))
  1174  
  1175  	b.WithContent("blog/sect2/b4/index.nn.md", pageContent("s2.b4"))
  1176  	b.WithContent("blog/sect2/b4/bp.nn.md", pageContent("s2.b4.bundlecontent.nn"))
  1177  
  1178  	b.WithTemplates("index.html", `
  1179  Num Pages: {{ len .Site.Pages }}
  1180  {{ range .Site.Pages }}
  1181  {{ .Kind }}|{{ .RelPermalink }}|Content: {{ .Title }}|Resources: {{ range .Resources }}R: {{ .Title }}|{{ .Content }}|{{ end -}}
  1182  {{ end }}
  1183  `)
  1184  
  1185  	b.Build(BuildCfg{})
  1186  
  1187  	b.AssertFileContent("public/nn/index.html",
  1188  		"Num Pages: 6",
  1189  		"page|/nn/blog/sect1/b1/|Content: s1.b1.nn|Resources: R: data.json|s1.b1.data|",
  1190  		"page|/nn/blog/sect2/b3/|Content: s2.b3|Resources: R: s2.b3.bundlecontent.nn|",
  1191  		"page|/nn/blog/sect2/b4/|Content: s2.b4|Resources: R: s2.b4.bundlecontent.nn",
  1192  	)
  1193  
  1194  	b.AssertFileContent("public/en/index.html",
  1195  		"Num Pages: 6",
  1196  		"section|/en/blog/sect2/|Content: s2|Resources: R: data.json|s2.data|",
  1197  		"page|/en/blog/sect2/b1/|Content: s2.b1|Resources: R: data.json|s2.b1.data|",
  1198  		"page|/en/blog/sect2/b2/|Content: s2.b2|Resources: R: s2.b2.bundlecontent|",
  1199  	)
  1200  }
  1201  
  1202  // #6208
  1203  func TestBundleIndexInSubFolder(t *testing.T) {
  1204  	config := `
  1205  baseURL = "https://example.com"
  1206  
  1207  `
  1208  
  1209  	const pageContent = `---
  1210  title: %q
  1211  ---
  1212  `
  1213  	createPage := func(s string) string {
  1214  		return fmt.Sprintf(pageContent, s)
  1215  	}
  1216  
  1217  	b := newTestSitesBuilder(t).WithConfigFile("toml", config)
  1218  	b.WithLogger(loggers.NewWarningLogger())
  1219  
  1220  	b.WithTemplates("_default/single.html", `{{ range .Resources }}
  1221  {{ .ResourceType }}|{{ .Title }}|
  1222  {{ end }}
  1223  
  1224  
  1225  `)
  1226  
  1227  	b.WithContent("bundle/index.md", createPage("bundle index"))
  1228  	b.WithContent("bundle/p1.md", createPage("bundle p1"))
  1229  	b.WithContent("bundle/sub/p2.md", createPage("bundle sub p2"))
  1230  	b.WithContent("bundle/sub/index.md", createPage("bundle sub index"))
  1231  	b.WithContent("bundle/sub/data.json", "data")
  1232  
  1233  	b.Build(BuildCfg{})
  1234  
  1235  	b.AssertFileContent("public/bundle/index.html", `
  1236          application|sub/data.json|
  1237          page|bundle p1|
  1238          page|bundle sub index|
  1239          page|bundle sub p2|
  1240  `)
  1241  }
  1242  
  1243  func TestBundleTransformMany(t *testing.T) {
  1244  	b := newTestSitesBuilder(t).WithSimpleConfigFile().Running()
  1245  
  1246  	for i := 1; i <= 50; i++ {
  1247  		b.WithContent(fmt.Sprintf("bundle%d/index.md", i), fmt.Sprintf(`
  1248  ---
  1249  title: "Page"
  1250  weight: %d
  1251  ---
  1252  
  1253  `, i))
  1254  		b.WithSourceFile(fmt.Sprintf("content/bundle%d/data.yaml", i), fmt.Sprintf(`data: v%d`, i))
  1255  		b.WithSourceFile(fmt.Sprintf("content/bundle%d/data.json", i), fmt.Sprintf(`{ "data": "v%d" }`, i))
  1256  		b.WithSourceFile(fmt.Sprintf("assets/data%d/data.yaml", i), fmt.Sprintf(`vdata: v%d`, i))
  1257  
  1258  	}
  1259  
  1260  	b.WithTemplatesAdded("_default/single.html", `
  1261  {{ $bundleYaml := .Resources.GetMatch "*.yaml" }}
  1262  {{ $bundleJSON := .Resources.GetMatch "*.json" }}
  1263  {{ $assetsYaml := resources.GetMatch (printf "data%d/*.yaml" .Weight) }}
  1264  {{ $data1 := $bundleYaml | transform.Unmarshal }}
  1265  {{ $data2 := $assetsYaml | transform.Unmarshal }}
  1266  {{ $bundleFingerprinted := $bundleYaml | fingerprint "md5" }}
  1267  {{ $assetsFingerprinted := $assetsYaml | fingerprint "md5" }}
  1268  {{ $jsonMin := $bundleJSON | minify }}
  1269  {{ $jsonMinMin := $jsonMin | minify }}
  1270  {{ $jsonMinMinMin := $jsonMinMin | minify }}
  1271  
  1272  data content unmarshaled: {{ $data1.data }}
  1273  data assets content unmarshaled: {{ $data2.vdata }}
  1274  bundle fingerprinted: {{ $bundleFingerprinted.RelPermalink }}
  1275  assets fingerprinted: {{ $assetsFingerprinted.RelPermalink }}
  1276  
  1277  bundle min min min: {{ $jsonMinMinMin.RelPermalink }}
  1278  bundle min min key: {{ $jsonMinMin.Key }}
  1279  
  1280  `)
  1281  
  1282  	for i := 0; i < 3; i++ {
  1283  
  1284  		b.Build(BuildCfg{})
  1285  
  1286  		for i := 1; i <= 50; i++ {
  1287  			index := fmt.Sprintf("public/bundle%d/index.html", i)
  1288  			b.AssertFileContent(fmt.Sprintf("public/bundle%d/data.yaml", i), fmt.Sprintf("data: v%d", i))
  1289  			b.AssertFileContent(index, fmt.Sprintf("data content unmarshaled: v%d", i))
  1290  			b.AssertFileContent(index, fmt.Sprintf("data assets content unmarshaled: v%d", i))
  1291  
  1292  			md5Asset := helpers.MD5String(fmt.Sprintf(`vdata: v%d`, i))
  1293  			b.AssertFileContent(index, fmt.Sprintf("assets fingerprinted: /data%d/data.%s.yaml", i, md5Asset))
  1294  
  1295  			// The original is not used, make sure it's not published.
  1296  			b.Assert(b.CheckExists(fmt.Sprintf("public/data%d/data.yaml", i)), qt.Equals, false)
  1297  
  1298  			md5Bundle := helpers.MD5String(fmt.Sprintf(`data: v%d`, i))
  1299  			b.AssertFileContent(index, fmt.Sprintf("bundle fingerprinted: /bundle%d/data.%s.yaml", i, md5Bundle))
  1300  
  1301  			b.AssertFileContent(index,
  1302  				fmt.Sprintf("bundle min min min: /bundle%d/data.min.min.min.json", i),
  1303  				fmt.Sprintf("bundle min min key: /bundle%d/data.min.min.json", i),
  1304  			)
  1305  			b.Assert(b.CheckExists(fmt.Sprintf("public/bundle%d/data.min.min.min.json", i)), qt.Equals, true)
  1306  			b.Assert(b.CheckExists(fmt.Sprintf("public/bundle%d/data.min.json", i)), qt.Equals, false)
  1307  			b.Assert(b.CheckExists(fmt.Sprintf("public/bundle%d/data.min.min.json", i)), qt.Equals, false)
  1308  
  1309  		}
  1310  
  1311  		b.EditFiles("assets/data/foo.yaml", "FOO")
  1312  
  1313  	}
  1314  }
  1315  
  1316  func TestPageBundlerHome(t *testing.T) {
  1317  	t.Parallel()
  1318  	c := qt.New(t)
  1319  
  1320  	workDir, clean, err := htesting.CreateTempDir(hugofs.Os, "hugo-bundler-home")
  1321  	c.Assert(err, qt.IsNil)
  1322  
  1323  	cfg := config.New()
  1324  	cfg.Set("workingDir", workDir)
  1325  	fs := hugofs.NewFrom(hugofs.Os, cfg)
  1326  
  1327  	os.MkdirAll(filepath.Join(workDir, "content"), 0777)
  1328  
  1329  	defer clean()
  1330  
  1331  	b := newTestSitesBuilder(t)
  1332  	b.Fs = fs
  1333  
  1334  	b.WithWorkingDir(workDir).WithViper(cfg)
  1335  
  1336  	b.WithContent("_index.md", "---\ntitle: Home\n---\n![Alt text](image.jpg)")
  1337  	b.WithSourceFile("content/data.json", "DATA")
  1338  
  1339  	b.WithTemplates("index.html", `Title: {{ .Title }}|First Resource: {{ index .Resources 0 }}|Content: {{ .Content }}`)
  1340  	b.WithTemplates("_default/_markup/render-image.html", `Hook Len Page Resources {{ len .Page.Resources }}`)
  1341  
  1342  	b.Build(BuildCfg{})
  1343  	b.AssertFileContent("public/index.html", `
  1344  Title: Home|First Resource: data.json|Content: <p>Hook Len Page Resources 1</p>
  1345  `)
  1346  }