github.com/linchen2chris/hugo@v0.0.0-20230307053224-cec209389705/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]any{
    77  							"CUSTOMO": map[string]any{
    78  								"mediaType":     "text/html",
    79  								"baseName":      "cindex",
    80  								"path":          "cpath",
    81  								"permalinkable": true,
    82  							},
    83  						})
    84  
    85  						cfg.Set("outputs", map[string]any{
    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("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("public", filename),
   138  							"TheContent",
   139  							"Single RelPermalink: "+rel,
   140  						)
   141  
   142  						b.AssertFileContent(filepath.FromSlash("public/images/hugo-logo.png"), "content")
   143  
   144  						// This should be just copied to destination.
   145  						b.AssertFileContent(filepath.FromSlash("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("public/root/index.html"), "Single RelPermalink: "+relURLBase+"/root/")
   163  							b.AssertFileContent(filepath.FromSlash("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("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("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("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("public/2017/pageslug/c/logo.png"), "content")
   224  						b.AssertFileContent(filepath.FromSlash("public/cpath/2017/pageslug/c/logo.png"), "content")
   225  						c.Assert(b.CheckExists("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("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("public/2017/pageslug.html"), "0: Page RelPermalink: |")
   251  
   252  							b.AssertFileContent(filepath.FromSlash("public/cpath/2017/pageslug.html"), "TheContent")
   253  
   254  							// 은행
   255  							b.AssertFileContent(filepath.FromSlash("public/c/은행/logo-은행.png"), "은행 PNG")
   256  
   257  						} else {
   258  							b.AssertFileContent(filepath.FromSlash("public/2017/pageslug/index.html"), "TheContent")
   259  							b.AssertFileContent(filepath.FromSlash("public/cpath/2017/pageslug/cindex.html"), "TheContent")
   260  							b.AssertFileContent(filepath.FromSlash("public/2017/pageslug/index.html"), "Single Title")
   261  							b.AssertFileContent(filepath.FromSlash("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  
   401  	// We need to use the OS fs for this.
   402  	workingDir, clean, err := htesting.CreateTempDir(hugofs.Os, "hugosym")
   403  	c.Assert(err, qt.IsNil)
   404  	cfg := config.NewWithTestDefaults()
   405  	cfg.Set("workingDir", workingDir)
   406  	fs := hugofs.NewFrom(hugofs.Os, cfg)
   407  
   408  	contentDirName := "content"
   409  
   410  	contentDir := filepath.Join(workingDir, contentDirName)
   411  	c.Assert(os.MkdirAll(filepath.Join(contentDir, "a"), 0777), qt.IsNil)
   412  
   413  	for i := 1; i <= 3; i++ {
   414  		c.Assert(os.MkdirAll(filepath.Join(workingDir, fmt.Sprintf("symcontent%d", i)), 0777), qt.IsNil)
   415  	}
   416  
   417  	c.Assert(os.MkdirAll(filepath.Join(workingDir, "symcontent2", "a1"), 0777), qt.IsNil)
   418  
   419  	// Symlinked sections inside content.
   420  	os.Chdir(contentDir)
   421  	for i := 1; i <= 3; i++ {
   422  		c.Assert(os.Symlink(filepath.FromSlash(fmt.Sprintf(("../symcontent%d"), i)), fmt.Sprintf("symbolic%d", i)), qt.IsNil)
   423  	}
   424  
   425  	c.Assert(os.Chdir(filepath.Join(contentDir, "a")), qt.IsNil)
   426  
   427  	// Create a symlink to one single content file
   428  	c.Assert(os.Symlink(filepath.FromSlash("../../symcontent2/a1/page.md"), "page_s.md"), qt.IsNil)
   429  
   430  	c.Assert(os.Chdir(filepath.FromSlash("../../symcontent3")), qt.IsNil)
   431  
   432  	// Create a circular symlink. Will print some warnings.
   433  	c.Assert(os.Symlink(filepath.Join("..", contentDirName), filepath.FromSlash("circus")), qt.IsNil)
   434  
   435  	c.Assert(os.Chdir(workingDir), qt.IsNil)
   436  
   437  	defer clean()
   438  
   439  	cfg.Set("workingDir", workingDir)
   440  	cfg.Set("contentDir", contentDirName)
   441  	cfg.Set("baseURL", "https://example.com")
   442  
   443  	layout := `{{ .Title }}|{{ .Content }}`
   444  	pageContent := `---
   445  slug: %s
   446  date: 2017-10-09
   447  ---
   448  
   449  TheContent.
   450  `
   451  
   452  	b := newTestSitesBuilderFromDepsCfg(t, deps.DepsCfg{
   453  		Fs:  fs,
   454  		Cfg: cfg,
   455  	})
   456  
   457  	b.WithTemplates(
   458  		"_default/single.html", layout,
   459  		"_default/list.html", layout,
   460  	)
   461  
   462  	b.WithContent(
   463  		"a/regular.md", fmt.Sprintf(pageContent, "a1"),
   464  	)
   465  
   466  	b.WithSourceFile(
   467  		"symcontent1/s1.md", fmt.Sprintf(pageContent, "s1"),
   468  		"symcontent1/s2.md", fmt.Sprintf(pageContent, "s2"),
   469  		// Regular files inside symlinked folder.
   470  		"symcontent1/s1.md", fmt.Sprintf(pageContent, "s1"),
   471  		"symcontent1/s2.md", fmt.Sprintf(pageContent, "s2"),
   472  
   473  		// A bundle
   474  		"symcontent2/a1/index.md", fmt.Sprintf(pageContent, ""),
   475  		"symcontent2/a1/page.md", fmt.Sprintf(pageContent, "page"),
   476  		"symcontent2/a1/logo.png", "image",
   477  
   478  		// Assets
   479  		"symcontent3/s1.png", "image",
   480  		"symcontent3/s2.png", "image",
   481  	)
   482  
   483  	b.Build(BuildCfg{})
   484  	s := b.H.Sites[0]
   485  
   486  	c.Assert(len(s.RegularPages()), qt.Equals, 7)
   487  	a1Bundle := s.getPage(page.KindPage, "symbolic2/a1/index.md")
   488  	c.Assert(a1Bundle, qt.Not(qt.IsNil))
   489  	c.Assert(len(a1Bundle.Resources()), qt.Equals, 2)
   490  	c.Assert(len(a1Bundle.Resources().ByType(pageResourceType)), qt.Equals, 1)
   491  
   492  	b.AssertFileContent(filepath.FromSlash("public/a/page/index.html"), "TheContent")
   493  	b.AssertFileContent(filepath.FromSlash("public/symbolic1/s1/index.html"), "TheContent")
   494  	b.AssertFileContent(filepath.FromSlash("public/symbolic2/a1/index.html"), "TheContent")
   495  }
   496  
   497  func TestPageBundlerHeadless(t *testing.T) {
   498  	t.Parallel()
   499  
   500  	cfg, fs := newTestCfg()
   501  	c := qt.New(t)
   502  
   503  	workDir := "/work"
   504  	cfg.Set("workingDir", workDir)
   505  	cfg.Set("contentDir", "base")
   506  	cfg.Set("baseURL", "https://example.com")
   507  
   508  	pageContent := `---
   509  title: "Bundle Galore"
   510  slug: s1
   511  date: 2017-01-23
   512  ---
   513  
   514  TheContent.
   515  
   516  {{< myShort >}}
   517  `
   518  
   519  	writeSource(t, fs, filepath.Join(workDir, "layouts", "_default", "single.html"), "single {{ .Content }}")
   520  	writeSource(t, fs, filepath.Join(workDir, "layouts", "_default", "list.html"), "list")
   521  	writeSource(t, fs, filepath.Join(workDir, "layouts", "shortcodes", "myShort.html"), "SHORTCODE")
   522  
   523  	writeSource(t, fs, filepath.Join(workDir, "base", "a", "index.md"), pageContent)
   524  	writeSource(t, fs, filepath.Join(workDir, "base", "a", "l1.png"), "PNG image")
   525  	writeSource(t, fs, filepath.Join(workDir, "base", "a", "l2.png"), "PNG image")
   526  
   527  	writeSource(t, fs, filepath.Join(workDir, "base", "b", "index.md"), `---
   528  title: "Headless Bundle in Topless Bar"
   529  slug: s2
   530  headless: true
   531  date: 2017-01-23
   532  ---
   533  
   534  TheContent.
   535  HEADLESS {{< myShort >}}
   536  `)
   537  	writeSource(t, fs, filepath.Join(workDir, "base", "b", "l1.png"), "PNG image")
   538  	writeSource(t, fs, filepath.Join(workDir, "base", "b", "l2.png"), "PNG image")
   539  	writeSource(t, fs, filepath.Join(workDir, "base", "b", "p1.md"), pageContent)
   540  
   541  	s := buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{})
   542  
   543  	c.Assert(len(s.RegularPages()), qt.Equals, 1)
   544  
   545  	regular := s.getPage(page.KindPage, "a/index")
   546  	c.Assert(regular.RelPermalink(), qt.Equals, "/s1/")
   547  
   548  	headless := s.getPage(page.KindPage, "b/index")
   549  	c.Assert(headless, qt.Not(qt.IsNil))
   550  	c.Assert(headless.Title(), qt.Equals, "Headless Bundle in Topless Bar")
   551  	c.Assert(headless.RelPermalink(), qt.Equals, "")
   552  	c.Assert(headless.Permalink(), qt.Equals, "")
   553  	c.Assert(content(headless), qt.Contains, "HEADLESS SHORTCODE")
   554  
   555  	headlessResources := headless.Resources()
   556  	c.Assert(len(headlessResources), qt.Equals, 3)
   557  	res := headlessResources.Match("l*")
   558  	c.Assert(len(res), qt.Equals, 2)
   559  	pageResource := headlessResources.GetMatch("p*")
   560  	c.Assert(pageResource, qt.Not(qt.IsNil))
   561  	p := pageResource.(page.Page)
   562  	c.Assert(content(p), qt.Contains, "SHORTCODE")
   563  	c.Assert(p.Name(), qt.Equals, "p1.md")
   564  
   565  	th := newTestHelper(s.Cfg, s.Fs, t)
   566  
   567  	th.assertFileContent(filepath.FromSlash("public/s1/index.html"), "TheContent")
   568  	th.assertFileContent(filepath.FromSlash("public/s1/l1.png"), "PNG")
   569  
   570  	th.assertFileNotExist("public/s2/index.html")
   571  	// But the bundled resources needs to be published
   572  	th.assertFileContent(filepath.FromSlash("public/s2/l1.png"), "PNG")
   573  
   574  	// No headless bundles here, please.
   575  	// https://github.com/gohugoio/hugo/issues/6492
   576  	c.Assert(s.RegularPages(), qt.HasLen, 1)
   577  	c.Assert(s.home.RegularPages(), qt.HasLen, 1)
   578  	c.Assert(s.home.Pages(), qt.HasLen, 1)
   579  }
   580  
   581  func TestPageBundlerHeadlessIssue6552(t *testing.T) {
   582  	t.Parallel()
   583  
   584  	b := newTestSitesBuilder(t)
   585  	b.WithContent("headless/h1/index.md", `
   586  ---
   587  title: My Headless Bundle1
   588  headless: true
   589  ---
   590  `, "headless/h1/p1.md", `
   591  ---
   592  title: P1
   593  ---
   594  `, "headless/h2/index.md", `
   595  ---
   596  title: My Headless Bundle2
   597  headless: true
   598  ---
   599  `)
   600  
   601  	b.WithTemplatesAdded("index.html", `
   602  {{ $headless1 := .Site.GetPage "headless/h1" }}
   603  {{ $headless2 := .Site.GetPage "headless/h2" }}
   604  
   605  HEADLESS1: {{ $headless1.Title }}|{{ $headless1.RelPermalink }}|{{ len $headless1.Resources }}|
   606  HEADLESS2: {{ $headless2.Title }}{{ $headless2.RelPermalink }}|{{ len $headless2.Resources }}|
   607  
   608  `)
   609  
   610  	b.Build(BuildCfg{})
   611  
   612  	b.AssertFileContent("public/index.html", `
   613  HEADLESS1: My Headless Bundle1||1|
   614  HEADLESS2: My Headless Bundle2|0|
   615  `)
   616  }
   617  
   618  func TestMultiSiteBundles(t *testing.T) {
   619  	c := qt.New(t)
   620  	b := newTestSitesBuilder(t)
   621  	b.WithConfigFile("toml", `
   622  
   623  baseURL = "http://example.com/"
   624  
   625  defaultContentLanguage = "en"
   626  
   627  [languages]
   628  [languages.en]
   629  weight = 10
   630  contentDir = "content/en"
   631  [languages.nn]
   632  weight = 20
   633  contentDir = "content/nn"
   634  
   635  
   636  `)
   637  
   638  	b.WithContent("en/mybundle/index.md", `
   639  ---
   640  headless: true
   641  ---
   642  
   643  `)
   644  
   645  	b.WithContent("nn/mybundle/index.md", `
   646  ---
   647  headless: true
   648  ---
   649  
   650  `)
   651  
   652  	b.WithContent("en/mybundle/data.yaml", `data en`)
   653  	b.WithContent("en/mybundle/forms.yaml", `forms en`)
   654  	b.WithContent("nn/mybundle/data.yaml", `data nn`)
   655  
   656  	b.WithContent("en/_index.md", `
   657  ---
   658  Title: Home
   659  ---
   660  
   661  Home content.
   662  
   663  `)
   664  
   665  	b.WithContent("en/section-not-bundle/_index.md", `
   666  ---
   667  Title: Section Page
   668  ---
   669  
   670  Section content.
   671  
   672  `)
   673  
   674  	b.WithContent("en/section-not-bundle/single.md", `
   675  ---
   676  Title: Section Single
   677  Date: 2018-02-01
   678  ---
   679  
   680  Single content.
   681  
   682  `)
   683  
   684  	b.Build(BuildCfg{})
   685  
   686  	b.AssertFileContent("public/nn/mybundle/data.yaml", "data nn")
   687  	b.AssertFileContent("public/nn/mybundle/forms.yaml", "forms en")
   688  	b.AssertFileContent("public/mybundle/data.yaml", "data en")
   689  	b.AssertFileContent("public/mybundle/forms.yaml", "forms en")
   690  
   691  	c.Assert(b.CheckExists("public/nn/nn/mybundle/data.yaml"), qt.Equals, false)
   692  	c.Assert(b.CheckExists("public/en/mybundle/data.yaml"), qt.Equals, false)
   693  
   694  	homeEn := b.H.Sites[0].home
   695  	c.Assert(homeEn, qt.Not(qt.IsNil))
   696  	c.Assert(homeEn.Date().Year(), qt.Equals, 2018)
   697  
   698  	b.AssertFileContent("public/section-not-bundle/index.html", "Section Page", "Content: <p>Section content.</p>")
   699  	b.AssertFileContent("public/section-not-bundle/single/index.html", "Section Single", "|<p>Single content.</p>")
   700  }
   701  
   702  func newTestBundleSources(t testing.TB) (*hugofs.Fs, config.Provider) {
   703  	cfg, fs := newTestCfgBasic()
   704  	c := qt.New(t)
   705  
   706  	workDir := "/work"
   707  	cfg.Set("workingDir", workDir)
   708  	cfg.Set("contentDir", "base")
   709  	cfg.Set("baseURL", "https://example.com")
   710  	cfg.Set("mediaTypes", map[string]any{
   711  		"bepsays/bep": map[string]any{
   712  			"suffixes": []string{"bep"},
   713  		},
   714  	})
   715  
   716  	pageContent := `---
   717  title: "Bundle Galore"
   718  slug: pageslug
   719  date: 2017-10-09
   720  ---
   721  
   722  TheContent.
   723  `
   724  
   725  	pageContentShortcode := `---
   726  title: "Bundle Galore"
   727  slug: pageslug
   728  date: 2017-10-09
   729  ---
   730  
   731  TheContent.
   732  
   733  {{< myShort >}}
   734  `
   735  
   736  	pageWithImageShortcodeAndResourceMetadataContent := `---
   737  title: "Bundle Galore"
   738  slug: pageslug
   739  date: 2017-10-09
   740  resources:
   741  - src: "*.jpg"
   742    name: "my-sunset-:counter"
   743    title: "Sunset Galore :counter"
   744    params:
   745      myParam: "My Sunny Param"
   746  ---
   747  
   748  TheContent.
   749  
   750  {{< myShort >}}
   751  `
   752  
   753  	pageContentNoSlug := `---
   754  title: "Bundle Galore #2"
   755  date: 2017-10-09
   756  ---
   757  
   758  TheContent.
   759  `
   760  
   761  	singleLayout := `
   762  Single Title: {{ .Title }}
   763  Single RelPermalink: {{ .RelPermalink }}
   764  Single Permalink: {{ .Permalink }}
   765  Content: {{ .Content }}
   766  {{ $sunset := .Resources.GetMatch "my-sunset-1*" }}
   767  {{ with $sunset }}
   768  Sunset RelPermalink: {{ .RelPermalink }}
   769  Sunset Permalink: {{ .Permalink }}
   770  {{ $thumb := .Fill "123x123" }}
   771  Thumb Width: {{ $thumb.Width }}
   772  Thumb Name: {{ $thumb.Name }}
   773  Thumb Title: {{ $thumb.Title }}
   774  Thumb RelPermalink: {{ $thumb.RelPermalink }}
   775  {{ end }}
   776  {{ $types := slice "image" "page" }}
   777  {{ range $types }}
   778  {{ $typeTitle := . | title }}
   779  {{ range $i, $e := $.Resources.ByType . }}
   780  {{ $i }}: {{ $typeTitle }} Title: {{ .Title }}
   781  {{ $i }}: {{ $typeTitle }} Name: {{ .Name }}
   782  {{ $i }}: {{ $typeTitle }} RelPermalink: {{ .RelPermalink }}|
   783  {{ $i }}: {{ $typeTitle }} Params: {{ printf "%v" .Params }}
   784  {{ $i }}: {{ $typeTitle }} myParam: Lower: {{ .Params.myparam }} Caps: {{ .Params.MYPARAM }}
   785  {{ end }}
   786  {{ end }}
   787  `
   788  
   789  	myShort := `
   790  MyShort in {{ .Page.File.Path }}:
   791  {{ $sunset := .Page.Resources.GetMatch "my-sunset-2*" }}
   792  {{ with $sunset }}
   793  Short Sunset RelPermalink: {{ .RelPermalink }}
   794  {{ $thumb := .Fill "56x56" }}
   795  Short Thumb Width: {{ $thumb.Width }}
   796  {{ end }}
   797  `
   798  
   799  	listLayout := `{{ .Title }}|{{ .Content }}`
   800  
   801  	writeSource(t, fs, filepath.Join(workDir, "layouts", "_default", "single.html"), singleLayout)
   802  	writeSource(t, fs, filepath.Join(workDir, "layouts", "_default", "list.html"), listLayout)
   803  	writeSource(t, fs, filepath.Join(workDir, "layouts", "shortcodes", "myShort.html"), myShort)
   804  	writeSource(t, fs, filepath.Join(workDir, "layouts", "shortcodes", "myShort.customo"), myShort)
   805  
   806  	writeSource(t, fs, filepath.Join(workDir, "base", "_index.md"), pageContent)
   807  	writeSource(t, fs, filepath.Join(workDir, "base", "_1.md"), pageContent)
   808  	writeSource(t, fs, filepath.Join(workDir, "base", "_1.png"), pageContent)
   809  
   810  	writeSource(t, fs, filepath.Join(workDir, "base", "images", "hugo-logo.png"), "content")
   811  	writeSource(t, fs, filepath.Join(workDir, "base", "a", "2.md"), pageContent)
   812  	writeSource(t, fs, filepath.Join(workDir, "base", "a", "1.md"), pageContent)
   813  
   814  	writeSource(t, fs, filepath.Join(workDir, "base", "a", "b", "index.md"), pageContentNoSlug)
   815  	writeSource(t, fs, filepath.Join(workDir, "base", "a", "b", "ab1.md"), pageContentNoSlug)
   816  
   817  	// Mostly plain static assets in a folder with a page in a sub folder thrown in.
   818  	writeSource(t, fs, filepath.Join(workDir, "base", "assets", "pic1.png"), "content")
   819  	writeSource(t, fs, filepath.Join(workDir, "base", "assets", "pic2.png"), "content")
   820  	writeSource(t, fs, filepath.Join(workDir, "base", "assets", "pages", "mypage.md"), pageContent)
   821  
   822  	// Bundle
   823  	writeSource(t, fs, filepath.Join(workDir, "base", "b", "my-bundle", "index.md"), pageWithImageShortcodeAndResourceMetadataContent)
   824  	writeSource(t, fs, filepath.Join(workDir, "base", "b", "my-bundle", "1.md"), pageContent)
   825  	writeSource(t, fs, filepath.Join(workDir, "base", "b", "my-bundle", "2.md"), pageContentShortcode)
   826  	writeSource(t, fs, filepath.Join(workDir, "base", "b", "my-bundle", "custom-mime.bep"), "bepsays")
   827  	writeSource(t, fs, filepath.Join(workDir, "base", "b", "my-bundle", "c", "logo.png"), "content")
   828  
   829  	// Bundle with 은행 slug
   830  	// See https://github.com/gohugoio/hugo/issues/4241
   831  	writeSource(t, fs, filepath.Join(workDir, "base", "c", "bundle", "index.md"), `---
   832  title: "은행 은행"
   833  slug: 은행
   834  date: 2017-10-09
   835  ---
   836  
   837  Content for 은행.
   838  `)
   839  
   840  	// Bundle in root
   841  	writeSource(t, fs, filepath.Join(workDir, "base", "root", "index.md"), pageWithImageShortcodeAndResourceMetadataContent)
   842  	writeSource(t, fs, filepath.Join(workDir, "base", "root", "1.md"), pageContent)
   843  	writeSource(t, fs, filepath.Join(workDir, "base", "root", "c", "logo.png"), "content")
   844  
   845  	writeSource(t, fs, filepath.Join(workDir, "base", "c", "bundle", "logo-은행.png"), "은행 PNG")
   846  
   847  	// Write a real image into one of the bundle above.
   848  	src, err := os.Open("testdata/sunset.jpg")
   849  	c.Assert(err, qt.IsNil)
   850  
   851  	// We need 2 to test https://github.com/gohugoio/hugo/issues/4202
   852  	out, err := fs.Source.Create(filepath.Join(workDir, "base", "b", "my-bundle", "sunset1.jpg"))
   853  	c.Assert(err, qt.IsNil)
   854  	out2, err := fs.Source.Create(filepath.Join(workDir, "base", "b", "my-bundle", "sunset2.jpg"))
   855  	c.Assert(err, qt.IsNil)
   856  
   857  	_, err = io.Copy(out, src)
   858  	c.Assert(err, qt.IsNil)
   859  	out.Close()
   860  	src.Seek(0, 0)
   861  	_, err = io.Copy(out2, src)
   862  	out2.Close()
   863  	src.Close()
   864  	c.Assert(err, qt.IsNil)
   865  
   866  	return fs, cfg
   867  }
   868  
   869  func newTestBundleSourcesMultilingual(t *testing.T) (*hugofs.Fs, config.Provider) {
   870  	cfg, fs := newTestCfgBasic()
   871  
   872  	workDir := "/work"
   873  	cfg.Set("workingDir", workDir)
   874  	cfg.Set("contentDir", "base")
   875  	cfg.Set("baseURL", "https://example.com")
   876  	cfg.Set("defaultContentLanguage", "en")
   877  
   878  	langConfig := map[string]any{
   879  		"en": map[string]any{
   880  			"weight":       1,
   881  			"languageName": "English",
   882  		},
   883  		"nn": map[string]any{
   884  			"weight":       2,
   885  			"languageName": "Nynorsk",
   886  		},
   887  	}
   888  
   889  	cfg.Set("languages", langConfig)
   890  
   891  	pageContent := `---
   892  slug: pageslug
   893  date: 2017-10-09
   894  ---
   895  
   896  TheContent.
   897  `
   898  
   899  	layout := `{{ .Title }}|{{ .Content }}|Lang: {{ .Site.Language.Lang }}`
   900  
   901  	writeSource(t, fs, filepath.Join(workDir, "layouts", "_default", "single.html"), layout)
   902  	writeSource(t, fs, filepath.Join(workDir, "layouts", "_default", "list.html"), layout)
   903  
   904  	writeSource(t, fs, filepath.Join(workDir, "base", "1s", "mypage.md"), pageContent)
   905  	writeSource(t, fs, filepath.Join(workDir, "base", "1s", "mypage.nn.md"), pageContent)
   906  	writeSource(t, fs, filepath.Join(workDir, "base", "1s", "mylogo.png"), "content")
   907  
   908  	writeSource(t, fs, filepath.Join(workDir, "base", "bb", "_index.md"), pageContent)
   909  	writeSource(t, fs, filepath.Join(workDir, "base", "bb", "_index.nn.md"), pageContent)
   910  	writeSource(t, fs, filepath.Join(workDir, "base", "bb", "en.md"), pageContent)
   911  	writeSource(t, fs, filepath.Join(workDir, "base", "bb", "_1.md"), pageContent)
   912  	writeSource(t, fs, filepath.Join(workDir, "base", "bb", "_1.nn.md"), pageContent)
   913  	writeSource(t, fs, filepath.Join(workDir, "base", "bb", "a.png"), "content")
   914  	writeSource(t, fs, filepath.Join(workDir, "base", "bb", "b.png"), "content")
   915  	writeSource(t, fs, filepath.Join(workDir, "base", "bb", "b.nn.png"), "content")
   916  	writeSource(t, fs, filepath.Join(workDir, "base", "bb", "c.nn.png"), "content")
   917  	writeSource(t, fs, filepath.Join(workDir, "base", "bb", "b", "d.nn.png"), "content")
   918  
   919  	writeSource(t, fs, filepath.Join(workDir, "base", "bc", "_index.md"), pageContent)
   920  	writeSource(t, fs, filepath.Join(workDir, "base", "bc", "_index.nn.md"), pageContent)
   921  	writeSource(t, fs, filepath.Join(workDir, "base", "bc", "page.md"), pageContent)
   922  	writeSource(t, fs, filepath.Join(workDir, "base", "bc", "logo-bc.png"), "logo")
   923  	writeSource(t, fs, filepath.Join(workDir, "base", "bc", "page.nn.md"), pageContent)
   924  	writeSource(t, fs, filepath.Join(workDir, "base", "bc", "data1.json"), "data1")
   925  	writeSource(t, fs, filepath.Join(workDir, "base", "bc", "data2.json"), "data2")
   926  	writeSource(t, fs, filepath.Join(workDir, "base", "bc", "data1.nn.json"), "data1.nn")
   927  
   928  	writeSource(t, fs, filepath.Join(workDir, "base", "bd", "index.md"), pageContent)
   929  	writeSource(t, fs, filepath.Join(workDir, "base", "bd", "page.md"), pageContent)
   930  	writeSource(t, fs, filepath.Join(workDir, "base", "bd", "page.nn.md"), pageContent)
   931  
   932  	writeSource(t, fs, filepath.Join(workDir, "base", "be", "_index.md"), pageContent)
   933  	writeSource(t, fs, filepath.Join(workDir, "base", "be", "page.md"), pageContent)
   934  	writeSource(t, fs, filepath.Join(workDir, "base", "be", "page.nn.md"), pageContent)
   935  
   936  	// Bundle leaf,  multilingual
   937  	writeSource(t, fs, filepath.Join(workDir, "base", "lb", "index.md"), pageContent)
   938  	writeSource(t, fs, filepath.Join(workDir, "base", "lb", "index.nn.md"), pageContent)
   939  	writeSource(t, fs, filepath.Join(workDir, "base", "lb", "1.md"), pageContent)
   940  	writeSource(t, fs, filepath.Join(workDir, "base", "lb", "2.md"), pageContent)
   941  	writeSource(t, fs, filepath.Join(workDir, "base", "lb", "2.nn.md"), pageContent)
   942  	writeSource(t, fs, filepath.Join(workDir, "base", "lb", "c", "page.md"), pageContent)
   943  	writeSource(t, fs, filepath.Join(workDir, "base", "lb", "c", "logo.png"), "content")
   944  	writeSource(t, fs, filepath.Join(workDir, "base", "lb", "c", "logo.nn.png"), "content")
   945  	writeSource(t, fs, filepath.Join(workDir, "base", "lb", "c", "one.png"), "content")
   946  	writeSource(t, fs, filepath.Join(workDir, "base", "lb", "c", "d", "deep.png"), "content")
   947  
   948  	// Translated bundle in some sensible sub path.
   949  	writeSource(t, fs, filepath.Join(workDir, "base", "bf", "my-bf-bundle", "index.md"), pageContent)
   950  	writeSource(t, fs, filepath.Join(workDir, "base", "bf", "my-bf-bundle", "index.nn.md"), pageContent)
   951  	writeSource(t, fs, filepath.Join(workDir, "base", "bf", "my-bf-bundle", "page.md"), pageContent)
   952  
   953  	return fs, cfg
   954  }
   955  
   956  // https://github.com/gohugoio/hugo/issues/5858
   957  func TestBundledResourcesWhenMultipleOutputFormats(t *testing.T) {
   958  	t.Parallel()
   959  
   960  	b := newTestSitesBuilder(t).Running().WithConfigFile("toml", `
   961  baseURL = "https://example.org"
   962  [outputs]
   963    # This looks odd, but it triggers the behaviour in #5858
   964    # The total output formats list gets sorted, so CSS before HTML.
   965    home = [ "CSS" ]
   966  
   967  `)
   968  	b.WithContent("mybundle/index.md", `
   969  ---
   970  title: Page
   971  date: 2017-01-15
   972  ---
   973  `,
   974  		"mybundle/data.json", "MyData",
   975  	)
   976  
   977  	b.CreateSites().Build(BuildCfg{})
   978  
   979  	b.AssertFileContent("public/mybundle/data.json", "MyData")
   980  
   981  	// Change the bundled JSON file and make sure it gets republished.
   982  	b.EditFiles("content/mybundle/data.json", "My changed data")
   983  
   984  	b.Build(BuildCfg{})
   985  
   986  	b.AssertFileContent("public/mybundle/data.json", "My changed data")
   987  }
   988  
   989  // https://github.com/gohugoio/hugo/issues/4870
   990  func TestBundleSlug(t *testing.T) {
   991  	t.Parallel()
   992  	c := qt.New(t)
   993  
   994  	const pageTemplate = `---
   995  title: Title
   996  slug: %s
   997  ---
   998  `
   999  
  1000  	b := newTestSitesBuilder(t)
  1001  
  1002  	b.WithTemplatesAdded("index.html", `{{ range .Site.RegularPages }}|{{ .RelPermalink }}{{ end }}|`)
  1003  	b.WithSimpleConfigFile().
  1004  		WithContent("about/services1/misc.md", fmt.Sprintf(pageTemplate, "this-is-the-slug")).
  1005  		WithContent("about/services2/misc/index.md", fmt.Sprintf(pageTemplate, "this-is-another-slug"))
  1006  
  1007  	b.CreateSites().Build(BuildCfg{})
  1008  
  1009  	b.AssertHome(
  1010  		"|/about/services1/this-is-the-slug/|/",
  1011  		"|/about/services2/this-is-another-slug/|")
  1012  
  1013  	c.Assert(b.CheckExists("public/about/services1/this-is-the-slug/index.html"), qt.Equals, true)
  1014  	c.Assert(b.CheckExists("public/about/services2/this-is-another-slug/index.html"), qt.Equals, true)
  1015  }
  1016  
  1017  func TestBundleMisc(t *testing.T) {
  1018  	config := `
  1019  baseURL = "https://example.com"
  1020  defaultContentLanguage = "en"
  1021  defaultContentLanguageInSubdir = true
  1022  ignoreFiles = ["README\\.md", "content/en/ignore"]
  1023  
  1024  [Languages]
  1025  [Languages.en]
  1026  weight = 99999
  1027  contentDir = "content/en"
  1028  [Languages.nn]
  1029  weight = 20
  1030  contentDir = "content/nn"
  1031  [Languages.sv]
  1032  weight = 30
  1033  contentDir = "content/sv"
  1034  [Languages.nb]
  1035  weight = 40
  1036  contentDir = "content/nb"
  1037  
  1038  `
  1039  
  1040  	const pageContent = `---
  1041  title: %q
  1042  ---
  1043  `
  1044  	createPage := func(s string) string {
  1045  		return fmt.Sprintf(pageContent, s)
  1046  	}
  1047  
  1048  	b := newTestSitesBuilder(t).WithConfigFile("toml", config)
  1049  	b.WithLogger(loggers.NewWarningLogger())
  1050  
  1051  	b.WithTemplates("_default/list.html", `{{ range .Site.Pages }}
  1052  {{ .Kind }}|{{ .Path }}|{{ with .CurrentSection }}CurrentSection: {{ .Path }}{{ end }}|{{ .RelPermalink }}{{ end }}
  1053  `)
  1054  
  1055  	b.WithTemplates("_default/single.html", `Single: {{ .Title }}`)
  1056  
  1057  	b.WithContent("en/sect1/sect2/_index.md", createPage("en: Sect 2"))
  1058  	b.WithContent("en/sect1/sect2/page.md", createPage("en: Page"))
  1059  	b.WithContent("en/sect1/sect2/data-branch.json", "mydata")
  1060  	b.WithContent("nn/sect1/sect2/page.md", createPage("nn: Page"))
  1061  	b.WithContent("nn/sect1/sect2/data-branch.json", "my nn data")
  1062  
  1063  	// En only
  1064  	b.WithContent("en/enonly/myen.md", createPage("en: Page"))
  1065  	b.WithContent("en/enonly/myendata.json", "mydata")
  1066  
  1067  	// Leaf
  1068  
  1069  	b.WithContent("nn/b1/index.md", createPage("nn: leaf"))
  1070  	b.WithContent("en/b1/index.md", createPage("en: leaf"))
  1071  	b.WithContent("sv/b1/index.md", createPage("sv: leaf"))
  1072  	b.WithContent("nb/b1/index.md", createPage("nb: leaf"))
  1073  
  1074  	// Should be ignored
  1075  	b.WithContent("en/ignore/page.md", createPage("en: ignore"))
  1076  	b.WithContent("en/README.md", createPage("en: ignore"))
  1077  
  1078  	// Both leaf and branch bundle in same dir
  1079  	b.WithContent("en/b2/index.md", `---
  1080  slug: leaf
  1081  ---
  1082  `)
  1083  	b.WithContent("en/b2/_index.md", createPage("en: branch"))
  1084  
  1085  	b.WithContent("en/b1/data1.json", "en: data")
  1086  	b.WithContent("sv/b1/data1.json", "sv: data")
  1087  	b.WithContent("sv/b1/data2.json", "sv: data2")
  1088  	b.WithContent("nb/b1/data2.json", "nb: data2")
  1089  
  1090  	b.WithContent("en/b3/_index.md", createPage("en: branch"))
  1091  	b.WithContent("en/b3/p1.md", createPage("en: page"))
  1092  	b.WithContent("en/b3/data1.json", "en: data")
  1093  
  1094  	b.Build(BuildCfg{})
  1095  
  1096  	b.AssertFileContent("public/en/index.html",
  1097  		filepath.FromSlash("section|sect1/sect2/_index.md|CurrentSection: sect1/sect2/_index.md"),
  1098  		"myen.md|CurrentSection: enonly")
  1099  
  1100  	b.AssertFileContentFn("public/en/index.html", func(s string) bool {
  1101  		// Check ignored files
  1102  		return !regexp.MustCompile("README|ignore").MatchString(s)
  1103  	})
  1104  
  1105  	b.AssertFileContent("public/nn/index.html", filepath.FromSlash("page|sect1/sect2/page.md|CurrentSection: sect1"))
  1106  	b.AssertFileContentFn("public/nn/index.html", func(s string) bool {
  1107  		return !strings.Contains(s, "enonly")
  1108  	})
  1109  
  1110  	// Check order of inherited data file
  1111  	b.AssertFileContent("public/nb/b1/data1.json", "en: data") // Default content
  1112  	b.AssertFileContent("public/nn/b1/data2.json", "sv: data") // First match
  1113  
  1114  	b.AssertFileContent("public/en/enonly/myen/index.html", "Single: en: Page")
  1115  	b.AssertFileContent("public/en/enonly/myendata.json", "mydata")
  1116  
  1117  	c := qt.New(t)
  1118  	c.Assert(b.CheckExists("public/sv/enonly/myen/index.html"), qt.Equals, false)
  1119  
  1120  	// Both leaf and branch bundle in same dir
  1121  	// We log a warning about it, but we keep both.
  1122  	b.AssertFileContent("public/en/b2/index.html",
  1123  		"/en/b2/leaf/",
  1124  		filepath.FromSlash("section|sect1/sect2/_index.md|CurrentSection: sect1/sect2/_index.md"))
  1125  }
  1126  
  1127  // Issue 6136
  1128  func TestPageBundlerPartialTranslations(t *testing.T) {
  1129  	config := `
  1130  baseURL = "https://example.org"
  1131  defaultContentLanguage = "en"
  1132  defaultContentLanguageInSubDir = true
  1133  disableKinds = ["taxonomy", "term"]
  1134  [languages]
  1135  [languages.nn]
  1136  languageName = "Nynorsk"
  1137  weight = 2
  1138  title = "Tittel på Nynorsk"
  1139  [languages.en]
  1140  title = "Title in English"
  1141  languageName = "English"
  1142  weight = 1
  1143  `
  1144  
  1145  	pageContent := func(id string) string {
  1146  		return fmt.Sprintf(`
  1147  ---
  1148  title: %q
  1149  ---
  1150  `, id)
  1151  	}
  1152  
  1153  	dataContent := func(id string) string {
  1154  		return id
  1155  	}
  1156  
  1157  	b := newTestSitesBuilder(t).WithConfigFile("toml", config)
  1158  
  1159  	b.WithContent("blog/sect1/_index.nn.md", pageContent("s1.nn"))
  1160  	b.WithContent("blog/sect1/data.json", dataContent("s1.data"))
  1161  
  1162  	b.WithContent("blog/sect1/b1/index.nn.md", pageContent("s1.b1.nn"))
  1163  	b.WithContent("blog/sect1/b1/data.json", dataContent("s1.b1.data"))
  1164  
  1165  	b.WithContent("blog/sect2/_index.md", pageContent("s2"))
  1166  	b.WithContent("blog/sect2/data.json", dataContent("s2.data"))
  1167  
  1168  	b.WithContent("blog/sect2/b1/index.md", pageContent("s2.b1"))
  1169  	b.WithContent("blog/sect2/b1/data.json", dataContent("s2.b1.data"))
  1170  
  1171  	b.WithContent("blog/sect2/b2/index.md", pageContent("s2.b2"))
  1172  	b.WithContent("blog/sect2/b2/bp.md", pageContent("s2.b2.bundlecontent"))
  1173  
  1174  	b.WithContent("blog/sect2/b3/index.md", pageContent("s2.b3"))
  1175  	b.WithContent("blog/sect2/b3/bp.nn.md", pageContent("s2.b3.bundlecontent.nn"))
  1176  
  1177  	b.WithContent("blog/sect2/b4/index.nn.md", pageContent("s2.b4"))
  1178  	b.WithContent("blog/sect2/b4/bp.nn.md", pageContent("s2.b4.bundlecontent.nn"))
  1179  
  1180  	b.WithTemplates("index.html", `
  1181  Num Pages: {{ len .Site.Pages }}
  1182  {{ range .Site.Pages }}
  1183  {{ .Kind }}|{{ .RelPermalink }}|Content: {{ .Title }}|Resources: {{ range .Resources }}R: {{ .Title }}|{{ .Content }}|{{ end -}}
  1184  {{ end }}
  1185  `)
  1186  
  1187  	b.Build(BuildCfg{})
  1188  
  1189  	b.AssertFileContent("public/nn/index.html",
  1190  		"Num Pages: 6",
  1191  		"page|/nn/blog/sect1/b1/|Content: s1.b1.nn|Resources: R: data.json|s1.b1.data|",
  1192  		"page|/nn/blog/sect2/b3/|Content: s2.b3|Resources: R: s2.b3.bundlecontent.nn|",
  1193  		"page|/nn/blog/sect2/b4/|Content: s2.b4|Resources: R: s2.b4.bundlecontent.nn",
  1194  	)
  1195  
  1196  	b.AssertFileContent("public/en/index.html",
  1197  		"Num Pages: 6",
  1198  		"section|/en/blog/sect2/|Content: s2|Resources: R: data.json|s2.data|",
  1199  		"page|/en/blog/sect2/b1/|Content: s2.b1|Resources: R: data.json|s2.b1.data|",
  1200  		"page|/en/blog/sect2/b2/|Content: s2.b2|Resources: R: s2.b2.bundlecontent|",
  1201  	)
  1202  }
  1203  
  1204  // #6208
  1205  func TestBundleIndexInSubFolder(t *testing.T) {
  1206  	config := `
  1207  baseURL = "https://example.com"
  1208  
  1209  `
  1210  
  1211  	const pageContent = `---
  1212  title: %q
  1213  ---
  1214  `
  1215  	createPage := func(s string) string {
  1216  		return fmt.Sprintf(pageContent, s)
  1217  	}
  1218  
  1219  	b := newTestSitesBuilder(t).WithConfigFile("toml", config)
  1220  	b.WithLogger(loggers.NewWarningLogger())
  1221  
  1222  	b.WithTemplates("_default/single.html", `{{ range .Resources }}
  1223  {{ .ResourceType }}|{{ .Title }}|
  1224  {{ end }}
  1225  
  1226  
  1227  `)
  1228  
  1229  	b.WithContent("bundle/index.md", createPage("bundle index"))
  1230  	b.WithContent("bundle/p1.md", createPage("bundle p1"))
  1231  	b.WithContent("bundle/sub/p2.md", createPage("bundle sub p2"))
  1232  	b.WithContent("bundle/sub/index.md", createPage("bundle sub index"))
  1233  	b.WithContent("bundle/sub/data.json", "data")
  1234  
  1235  	b.Build(BuildCfg{})
  1236  
  1237  	b.AssertFileContent("public/bundle/index.html", `
  1238          application|sub/data.json|
  1239          page|bundle p1|
  1240          page|bundle sub index|
  1241          page|bundle sub p2|
  1242  `)
  1243  }
  1244  
  1245  func TestBundleTransformMany(t *testing.T) {
  1246  	b := newTestSitesBuilder(t).WithSimpleConfigFile().Running()
  1247  
  1248  	for i := 1; i <= 50; i++ {
  1249  		b.WithContent(fmt.Sprintf("bundle%d/index.md", i), fmt.Sprintf(`
  1250  ---
  1251  title: "Page"
  1252  weight: %d
  1253  ---
  1254  
  1255  `, i))
  1256  		b.WithSourceFile(fmt.Sprintf("content/bundle%d/data.yaml", i), fmt.Sprintf(`data: v%d`, i))
  1257  		b.WithSourceFile(fmt.Sprintf("content/bundle%d/data.json", i), fmt.Sprintf(`{ "data": "v%d" }`, i))
  1258  		b.WithSourceFile(fmt.Sprintf("assets/data%d/data.yaml", i), fmt.Sprintf(`vdata: v%d`, i))
  1259  
  1260  	}
  1261  
  1262  	b.WithTemplatesAdded("_default/single.html", `
  1263  {{ $bundleYaml := .Resources.GetMatch "*.yaml" }}
  1264  {{ $bundleJSON := .Resources.GetMatch "*.json" }}
  1265  {{ $assetsYaml := resources.GetMatch (printf "data%d/*.yaml" .Weight) }}
  1266  {{ $data1 := $bundleYaml | transform.Unmarshal }}
  1267  {{ $data2 := $assetsYaml | transform.Unmarshal }}
  1268  {{ $bundleFingerprinted := $bundleYaml | fingerprint "md5" }}
  1269  {{ $assetsFingerprinted := $assetsYaml | fingerprint "md5" }}
  1270  {{ $jsonMin := $bundleJSON | minify }}
  1271  {{ $jsonMinMin := $jsonMin | minify }}
  1272  {{ $jsonMinMinMin := $jsonMinMin | minify }}
  1273  
  1274  data content unmarshaled: {{ $data1.data }}
  1275  data assets content unmarshaled: {{ $data2.vdata }}
  1276  bundle fingerprinted: {{ $bundleFingerprinted.RelPermalink }}
  1277  assets fingerprinted: {{ $assetsFingerprinted.RelPermalink }}
  1278  
  1279  bundle min min min: {{ $jsonMinMinMin.RelPermalink }}
  1280  bundle min min key: {{ $jsonMinMin.Key }}
  1281  
  1282  `)
  1283  
  1284  	for i := 0; i < 3; i++ {
  1285  
  1286  		b.Build(BuildCfg{})
  1287  
  1288  		for i := 1; i <= 50; i++ {
  1289  			index := fmt.Sprintf("public/bundle%d/index.html", i)
  1290  			b.AssertFileContent(fmt.Sprintf("public/bundle%d/data.yaml", i), fmt.Sprintf("data: v%d", i))
  1291  			b.AssertFileContent(index, fmt.Sprintf("data content unmarshaled: v%d", i))
  1292  			b.AssertFileContent(index, fmt.Sprintf("data assets content unmarshaled: v%d", i))
  1293  
  1294  			md5Asset := helpers.MD5String(fmt.Sprintf(`vdata: v%d`, i))
  1295  			b.AssertFileContent(index, fmt.Sprintf("assets fingerprinted: /data%d/data.%s.yaml", i, md5Asset))
  1296  
  1297  			// The original is not used, make sure it's not published.
  1298  			b.Assert(b.CheckExists(fmt.Sprintf("public/data%d/data.yaml", i)), qt.Equals, false)
  1299  
  1300  			md5Bundle := helpers.MD5String(fmt.Sprintf(`data: v%d`, i))
  1301  			b.AssertFileContent(index, fmt.Sprintf("bundle fingerprinted: /bundle%d/data.%s.yaml", i, md5Bundle))
  1302  
  1303  			b.AssertFileContent(index,
  1304  				fmt.Sprintf("bundle min min min: /bundle%d/data.min.min.min.json", i),
  1305  				fmt.Sprintf("bundle min min key: /bundle%d/data.min.min.json", i),
  1306  			)
  1307  			b.Assert(b.CheckExists(fmt.Sprintf("public/bundle%d/data.min.min.min.json", i)), qt.Equals, true)
  1308  			b.Assert(b.CheckExists(fmt.Sprintf("public/bundle%d/data.min.json", i)), qt.Equals, false)
  1309  			b.Assert(b.CheckExists(fmt.Sprintf("public/bundle%d/data.min.min.json", i)), qt.Equals, false)
  1310  
  1311  		}
  1312  
  1313  		b.EditFiles("assets/data/foo.yaml", "FOO")
  1314  
  1315  	}
  1316  }
  1317  
  1318  func TestPageBundlerHome(t *testing.T) {
  1319  	t.Parallel()
  1320  	c := qt.New(t)
  1321  
  1322  	workDir, clean, err := htesting.CreateTempDir(hugofs.Os, "hugo-bundler-home")
  1323  	c.Assert(err, qt.IsNil)
  1324  
  1325  	cfg := config.NewWithTestDefaults()
  1326  	cfg.Set("workingDir", workDir)
  1327  	fs := hugofs.NewFrom(hugofs.Os, cfg)
  1328  
  1329  	os.MkdirAll(filepath.Join(workDir, "content"), 0777)
  1330  
  1331  	defer clean()
  1332  
  1333  	b := newTestSitesBuilder(t)
  1334  	b.Fs = fs
  1335  
  1336  	b.WithWorkingDir(workDir).WithViper(cfg)
  1337  
  1338  	b.WithContent("_index.md", "---\ntitle: Home\n---\n![Alt text](image.jpg)")
  1339  	b.WithSourceFile("content/data.json", "DATA")
  1340  
  1341  	b.WithTemplates("index.html", `Title: {{ .Title }}|First Resource: {{ index .Resources 0 }}|Content: {{ .Content }}`)
  1342  	b.WithTemplates("_default/_markup/render-image.html", `Hook Len Page Resources {{ len .Page.Resources }}`)
  1343  
  1344  	b.Build(BuildCfg{})
  1345  	b.AssertFileContent("public/index.html", `
  1346  Title: Home|First Resource: data.json|Content: <p>Hook Len Page Resources 1</p>
  1347  `)
  1348  }