hugo

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

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

pagebundler_test.go (44549B)

    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 }