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 }