github.com/kovansky/hugo@v0.92.3-0.20220224232819-63076e4ff19f/hugolib/hugo_sites_build_test.go (about) 1 package hugolib 2 3 import ( 4 "fmt" 5 "path/filepath" 6 "strings" 7 "testing" 8 "time" 9 10 qt "github.com/frankban/quicktest" 11 "github.com/gohugoio/hugo/htesting" 12 "github.com/gohugoio/hugo/resources/page" 13 14 "github.com/fortytw2/leaktest" 15 "github.com/fsnotify/fsnotify" 16 "github.com/gohugoio/hugo/helpers" 17 "github.com/gohugoio/hugo/hugofs" 18 "github.com/spf13/afero" 19 ) 20 21 func TestMultiSitesMainLangInRoot(t *testing.T) { 22 t.Parallel() 23 for _, b := range []bool{false} { 24 doTestMultiSitesMainLangInRoot(t, b) 25 } 26 } 27 28 func doTestMultiSitesMainLangInRoot(t *testing.T, defaultInSubDir bool) { 29 c := qt.New(t) 30 31 siteConfig := map[string]interface{}{ 32 "DefaultContentLanguage": "fr", 33 "DefaultContentLanguageInSubdir": defaultInSubDir, 34 } 35 36 b := newMultiSiteTestBuilder(t, "toml", multiSiteTOMLConfigTemplate, siteConfig) 37 38 pathMod := func(s string) string { 39 return s 40 } 41 42 if !defaultInSubDir { 43 pathMod = func(s string) string { 44 return strings.Replace(s, "/fr/", "/", -1) 45 } 46 } 47 48 b.CreateSites() 49 b.Build(BuildCfg{}) 50 51 sites := b.H.Sites 52 c.Assert(len(sites), qt.Equals, 4) 53 54 enSite := sites[0] 55 frSite := sites[1] 56 57 c.Assert(enSite.Info.LanguagePrefix, qt.Equals, "/en") 58 59 if defaultInSubDir { 60 c.Assert(frSite.Info.LanguagePrefix, qt.Equals, "/fr") 61 } else { 62 c.Assert(frSite.Info.LanguagePrefix, qt.Equals, "") 63 } 64 65 c.Assert(enSite.PathSpec.RelURL("foo", true), qt.Equals, "/blog/en/foo") 66 67 doc1en := enSite.RegularPages()[0] 68 doc1fr := frSite.RegularPages()[0] 69 70 enPerm := doc1en.Permalink() 71 enRelPerm := doc1en.RelPermalink() 72 c.Assert(enPerm, qt.Equals, "http://example.com/blog/en/sect/doc1-slug/") 73 c.Assert(enRelPerm, qt.Equals, "/blog/en/sect/doc1-slug/") 74 75 frPerm := doc1fr.Permalink() 76 frRelPerm := doc1fr.RelPermalink() 77 78 b.AssertFileContent(pathMod("public/fr/sect/doc1/index.html"), "Single", "Bonjour") 79 b.AssertFileContent("public/en/sect/doc1-slug/index.html", "Single", "Hello") 80 81 if defaultInSubDir { 82 c.Assert(frPerm, qt.Equals, "http://example.com/blog/fr/sect/doc1/") 83 c.Assert(frRelPerm, qt.Equals, "/blog/fr/sect/doc1/") 84 85 // should have a redirect on top level. 86 b.AssertFileContent("public/index.html", `<meta http-equiv="refresh" content="0; url=http://example.com/blog/fr" />`) 87 } else { 88 // Main language in root 89 c.Assert(frPerm, qt.Equals, "http://example.com/blog/sect/doc1/") 90 c.Assert(frRelPerm, qt.Equals, "/blog/sect/doc1/") 91 92 // should have redirect back to root 93 b.AssertFileContent("public/fr/index.html", `<meta http-equiv="refresh" content="0; url=http://example.com/blog" />`) 94 } 95 b.AssertFileContent(pathMod("public/fr/index.html"), "Home", "Bonjour") 96 b.AssertFileContent("public/en/index.html", "Home", "Hello") 97 98 // Check list pages 99 b.AssertFileContent(pathMod("public/fr/sect/index.html"), "List", "Bonjour") 100 b.AssertFileContent("public/en/sect/index.html", "List", "Hello") 101 b.AssertFileContent(pathMod("public/fr/plaques/FRtag1/index.html"), "Taxonomy List", "Bonjour") 102 b.AssertFileContent("public/en/tags/tag1/index.html", "Taxonomy List", "Hello") 103 104 // Check sitemaps 105 // Sitemaps behaves different: In a multilanguage setup there will always be a index file and 106 // one sitemap in each lang folder. 107 b.AssertFileContent("public/sitemap.xml", 108 "<loc>http://example.com/blog/en/sitemap.xml</loc>", 109 "<loc>http://example.com/blog/fr/sitemap.xml</loc>") 110 111 if defaultInSubDir { 112 b.AssertFileContent("public/fr/sitemap.xml", "<loc>http://example.com/blog/fr/</loc>") 113 } else { 114 b.AssertFileContent("public/fr/sitemap.xml", "<loc>http://example.com/blog/</loc>") 115 } 116 b.AssertFileContent("public/en/sitemap.xml", "<loc>http://example.com/blog/en/</loc>") 117 118 // Check rss 119 b.AssertFileContent(pathMod("public/fr/index.xml"), pathMod(`<atom:link href="http://example.com/blog/fr/index.xml"`), 120 `rel="self" type="application/rss+xml"`) 121 b.AssertFileContent("public/en/index.xml", `<atom:link href="http://example.com/blog/en/index.xml"`) 122 b.AssertFileContent( 123 pathMod("public/fr/sect/index.xml"), 124 pathMod(`<atom:link href="http://example.com/blog/fr/sect/index.xml"`)) 125 b.AssertFileContent("public/en/sect/index.xml", `<atom:link href="http://example.com/blog/en/sect/index.xml"`) 126 b.AssertFileContent( 127 pathMod("public/fr/plaques/FRtag1/index.xml"), 128 pathMod(`<atom:link href="http://example.com/blog/fr/plaques/FRtag1/index.xml"`)) 129 b.AssertFileContent("public/en/tags/tag1/index.xml", `<atom:link href="http://example.com/blog/en/tags/tag1/index.xml"`) 130 131 // Check paginators 132 b.AssertFileContent(pathMod("public/fr/page/1/index.html"), pathMod(`refresh" content="0; url=http://example.com/blog/fr/"`)) 133 b.AssertFileContent("public/en/page/1/index.html", `refresh" content="0; url=http://example.com/blog/en/"`) 134 b.AssertFileContent(pathMod("public/fr/page/2/index.html"), "Home Page 2", "Bonjour", pathMod("http://example.com/blog/fr/")) 135 b.AssertFileContent("public/en/page/2/index.html", "Home Page 2", "Hello", "http://example.com/blog/en/") 136 b.AssertFileContent(pathMod("public/fr/sect/page/1/index.html"), pathMod(`refresh" content="0; url=http://example.com/blog/fr/sect/"`)) 137 b.AssertFileContent("public/en/sect/page/1/index.html", `refresh" content="0; url=http://example.com/blog/en/sect/"`) 138 b.AssertFileContent(pathMod("public/fr/sect/page/2/index.html"), "List Page 2", "Bonjour", pathMod("http://example.com/blog/fr/sect/")) 139 b.AssertFileContent("public/en/sect/page/2/index.html", "List Page 2", "Hello", "http://example.com/blog/en/sect/") 140 b.AssertFileContent( 141 pathMod("public/fr/plaques/FRtag1/page/1/index.html"), 142 pathMod(`refresh" content="0; url=http://example.com/blog/fr/plaques/FRtag1/"`)) 143 b.AssertFileContent("public/en/tags/tag1/page/1/index.html", `refresh" content="0; url=http://example.com/blog/en/tags/tag1/"`) 144 b.AssertFileContent( 145 pathMod("public/fr/plaques/FRtag1/page/2/index.html"), "List Page 2", "Bonjour", 146 pathMod("http://example.com/blog/fr/plaques/FRtag1/")) 147 b.AssertFileContent("public/en/tags/tag1/page/2/index.html", "List Page 2", "Hello", "http://example.com/blog/en/tags/tag1/") 148 // nn (Nynorsk) and nb (Bokmål) have custom pagePath: side ("page" in Norwegian) 149 b.AssertFileContent("public/nn/side/1/index.html", `refresh" content="0; url=http://example.com/blog/nn/"`) 150 b.AssertFileContent("public/nb/side/1/index.html", `refresh" content="0; url=http://example.com/blog/nb/"`) 151 } 152 153 func TestMultiSitesWithTwoLanguages(t *testing.T) { 154 t.Parallel() 155 156 c := qt.New(t) 157 b := newTestSitesBuilder(t).WithConfigFile("toml", ` 158 159 defaultContentLanguage = "nn" 160 161 [languages] 162 [languages.nn] 163 languageName = "Nynorsk" 164 weight = 1 165 title = "Tittel på Nynorsk" 166 [languages.nn.params] 167 p1 = "p1nn" 168 169 [languages.en] 170 title = "Title in English" 171 languageName = "English" 172 weight = 2 173 [languages.en.params] 174 p1 = "p1en" 175 `) 176 177 b.CreateSites() 178 b.Build(BuildCfg{SkipRender: true}) 179 sites := b.H.Sites 180 181 c.Assert(len(sites), qt.Equals, 2) 182 183 nnSite := sites[0] 184 nnHome := nnSite.getPage(page.KindHome) 185 c.Assert(len(nnHome.AllTranslations()), qt.Equals, 2) 186 c.Assert(len(nnHome.Translations()), qt.Equals, 1) 187 c.Assert(nnHome.IsTranslated(), qt.Equals, true) 188 189 enHome := sites[1].getPage(page.KindHome) 190 191 p1, err := enHome.Param("p1") 192 c.Assert(err, qt.IsNil) 193 c.Assert(p1, qt.Equals, "p1en") 194 195 p1, err = nnHome.Param("p1") 196 c.Assert(err, qt.IsNil) 197 c.Assert(p1, qt.Equals, "p1nn") 198 } 199 200 func TestMultiSitesBuild(t *testing.T) { 201 for _, config := range []struct { 202 content string 203 suffix string 204 }{ 205 {multiSiteTOMLConfigTemplate, "toml"}, 206 {multiSiteYAMLConfigTemplate, "yml"}, 207 {multiSiteJSONConfigTemplate, "json"}, 208 } { 209 t.Run(config.suffix, func(t *testing.T) { 210 t.Parallel() 211 doTestMultiSitesBuild(t, config.content, config.suffix) 212 }) 213 } 214 } 215 216 func doTestMultiSitesBuild(t *testing.T, configTemplate, configSuffix string) { 217 c := qt.New(t) 218 219 b := newMultiSiteTestBuilder(t, configSuffix, configTemplate, nil) 220 b.CreateSites() 221 222 sites := b.H.Sites 223 c.Assert(len(sites), qt.Equals, 4) 224 225 b.Build(BuildCfg{}) 226 227 // Check site config 228 for _, s := range sites { 229 c.Assert(s.Info.defaultContentLanguageInSubdir, qt.Equals, true) 230 c.Assert(s.disabledKinds, qt.Not(qt.IsNil)) 231 } 232 233 gp1 := b.H.GetContentPage(filepath.FromSlash("content/sect/doc1.en.md")) 234 c.Assert(gp1, qt.Not(qt.IsNil)) 235 c.Assert(gp1.Title(), qt.Equals, "doc1") 236 gp2 := b.H.GetContentPage(filepath.FromSlash("content/dummysect/notfound.md")) 237 c.Assert(gp2, qt.IsNil) 238 239 enSite := sites[0] 240 enSiteHome := enSite.getPage(page.KindHome) 241 c.Assert(enSiteHome.IsTranslated(), qt.Equals, true) 242 243 c.Assert(enSite.language.Lang, qt.Equals, "en") 244 245 // dumpPages(enSite.RegularPages()...) 246 247 c.Assert(len(enSite.RegularPages()), qt.Equals, 5) 248 c.Assert(len(enSite.AllPages()), qt.Equals, 32) 249 250 // Check 404s 251 b.AssertFileContent("public/en/404.html", "404|en|404 Page not found") 252 b.AssertFileContent("public/fr/404.html", "404|fr|404 Page not found") 253 254 // Check robots.txt 255 // the domain root is the public directory, so the robots.txt has to be created there and not in the language directories 256 b.AssertFileContent("public/robots.txt", "robots") 257 b.AssertFileDoesNotExist("public/en/robots.txt") 258 b.AssertFileDoesNotExist("public/nn/robots.txt") 259 260 b.AssertFileContent("public/en/sect/doc1-slug/index.html", "Permalink: http://example.com/blog/en/sect/doc1-slug/") 261 b.AssertFileContent("public/en/sect/doc2/index.html", "Permalink: http://example.com/blog/en/sect/doc2/") 262 b.AssertFileContent("public/superbob/index.html", "Permalink: http://example.com/blog/superbob/") 263 264 doc2 := enSite.RegularPages()[1] 265 doc3 := enSite.RegularPages()[2] 266 c.Assert(doc3, qt.Equals, doc2.Prev()) 267 doc1en := enSite.RegularPages()[0] 268 doc1fr := doc1en.Translations()[0] 269 b.AssertFileContent("public/fr/sect/doc1/index.html", "Permalink: http://example.com/blog/fr/sect/doc1/") 270 271 c.Assert(doc1fr, qt.Equals, doc1en.Translations()[0]) 272 c.Assert(doc1en, qt.Equals, doc1fr.Translations()[0]) 273 c.Assert(doc1fr.Language().Lang, qt.Equals, "fr") 274 275 doc4 := enSite.AllPages()[4] 276 c.Assert(len(doc4.Translations()), qt.Equals, 0) 277 278 // Taxonomies and their URLs 279 c.Assert(len(enSite.Taxonomies()), qt.Equals, 1) 280 tags := enSite.Taxonomies()["tags"] 281 c.Assert(len(tags), qt.Equals, 2) 282 c.Assert(doc1en, qt.Equals, tags["tag1"][0].Page) 283 284 frSite := sites[1] 285 286 c.Assert(frSite.language.Lang, qt.Equals, "fr") 287 c.Assert(len(frSite.RegularPages()), qt.Equals, 4) 288 c.Assert(len(frSite.AllPages()), qt.Equals, 32) 289 290 for _, frenchPage := range frSite.RegularPages() { 291 p := frenchPage 292 c.Assert(p.Language().Lang, qt.Equals, "fr") 293 } 294 295 // See https://github.com/gohugoio/hugo/issues/4285 296 // Before Hugo 0.33 you had to be explicit with the content path to get the correct Page, which 297 // isn't ideal in a multilingual setup. You want a way to get the current language version if available. 298 // Now you can do lookups with translation base name to get that behaviour. 299 // Let us test all the regular page variants: 300 getPageDoc1En := enSite.getPage(page.KindPage, filepath.ToSlash(doc1en.File().Path())) 301 getPageDoc1EnBase := enSite.getPage(page.KindPage, "sect/doc1") 302 getPageDoc1Fr := frSite.getPage(page.KindPage, filepath.ToSlash(doc1fr.File().Path())) 303 getPageDoc1FrBase := frSite.getPage(page.KindPage, "sect/doc1") 304 c.Assert(getPageDoc1En, qt.Equals, doc1en) 305 c.Assert(getPageDoc1Fr, qt.Equals, doc1fr) 306 c.Assert(getPageDoc1EnBase, qt.Equals, doc1en) 307 c.Assert(getPageDoc1FrBase, qt.Equals, doc1fr) 308 309 // Check redirect to main language, French 310 b.AssertFileContent("public/index.html", "0; url=http://example.com/blog/fr") 311 312 // check home page content (including data files rendering) 313 b.AssertFileContent("public/en/index.html", "Default Home Page 1", "Hello", "Hugo Rocks!") 314 b.AssertFileContent("public/fr/index.html", "French Home Page 1", "Bonjour", "Hugo Rocks!") 315 316 // check single page content 317 b.AssertFileContent("public/fr/sect/doc1/index.html", "Single", "Shortcode: Bonjour", "LingoFrench") 318 b.AssertFileContent("public/en/sect/doc1-slug/index.html", "Single", "Shortcode: Hello", "LingoDefault") 319 320 // Check node translations 321 homeEn := enSite.getPage(page.KindHome) 322 c.Assert(homeEn, qt.Not(qt.IsNil)) 323 c.Assert(len(homeEn.Translations()), qt.Equals, 3) 324 c.Assert(homeEn.Translations()[0].Language().Lang, qt.Equals, "fr") 325 c.Assert(homeEn.Translations()[1].Language().Lang, qt.Equals, "nn") 326 c.Assert(homeEn.Translations()[1].Title(), qt.Equals, "På nynorsk") 327 c.Assert(homeEn.Translations()[2].Language().Lang, qt.Equals, "nb") 328 c.Assert(homeEn.Translations()[2].Title(), qt.Equals, "På bokmål") 329 c.Assert(homeEn.Translations()[2].Language().LanguageName, qt.Equals, "Bokmål") 330 331 sectFr := frSite.getPage(page.KindSection, "sect") 332 c.Assert(sectFr, qt.Not(qt.IsNil)) 333 334 c.Assert(sectFr.Language().Lang, qt.Equals, "fr") 335 c.Assert(len(sectFr.Translations()), qt.Equals, 1) 336 c.Assert(sectFr.Translations()[0].Language().Lang, qt.Equals, "en") 337 c.Assert(sectFr.Translations()[0].Title(), qt.Equals, "Sects") 338 339 nnSite := sites[2] 340 c.Assert(nnSite.language.Lang, qt.Equals, "nn") 341 taxNn := nnSite.getPage(page.KindTaxonomy, "lag") 342 c.Assert(taxNn, qt.Not(qt.IsNil)) 343 c.Assert(len(taxNn.Translations()), qt.Equals, 1) 344 c.Assert(taxNn.Translations()[0].Language().Lang, qt.Equals, "nb") 345 346 taxTermNn := nnSite.getPage(page.KindTerm, "lag", "sogndal") 347 c.Assert(taxTermNn, qt.Not(qt.IsNil)) 348 c.Assert(nnSite.getPage(page.KindTerm, "LAG", "SOGNDAL"), qt.Equals, taxTermNn) 349 c.Assert(len(taxTermNn.Translations()), qt.Equals, 1) 350 c.Assert(taxTermNn.Translations()[0].Language().Lang, qt.Equals, "nb") 351 352 // Check sitemap(s) 353 b.AssertFileContent("public/sitemap.xml", 354 "<loc>http://example.com/blog/en/sitemap.xml</loc>", 355 "<loc>http://example.com/blog/fr/sitemap.xml</loc>") 356 b.AssertFileContent("public/en/sitemap.xml", "http://example.com/blog/en/sect/doc2/") 357 b.AssertFileContent("public/fr/sitemap.xml", "http://example.com/blog/fr/sect/doc1/") 358 359 // Check taxonomies 360 enTags := enSite.Taxonomies()["tags"] 361 frTags := frSite.Taxonomies()["plaques"] 362 c.Assert(len(enTags), qt.Equals, 2, qt.Commentf("Tags in en: %v", enTags)) 363 c.Assert(len(frTags), qt.Equals, 2, qt.Commentf("Tags in fr: %v", frTags)) 364 c.Assert(enTags["tag1"], qt.Not(qt.IsNil)) 365 c.Assert(frTags["FRtag1"], qt.Not(qt.IsNil)) 366 b.AssertFileContent("public/fr/plaques/FRtag1/index.html", "FRtag1|Bonjour|http://example.com/blog/fr/plaques/FRtag1/") 367 368 // Check Blackfriday config 369 c.Assert(strings.Contains(content(doc1fr), "«"), qt.Equals, true) 370 c.Assert(strings.Contains(content(doc1en), "«"), qt.Equals, false) 371 c.Assert(strings.Contains(content(doc1en), "“"), qt.Equals, true) 372 373 // en and nn have custom site menus 374 c.Assert(len(frSite.Menus()), qt.Equals, 0) 375 c.Assert(len(enSite.Menus()), qt.Equals, 1) 376 c.Assert(len(nnSite.Menus()), qt.Equals, 1) 377 378 c.Assert(enSite.Menus()["main"].ByName()[0].Name, qt.Equals, "Home") 379 c.Assert(nnSite.Menus()["main"].ByName()[0].Name, qt.Equals, "Heim") 380 381 // Issue #3108 382 prevPage := enSite.RegularPages()[0].Prev() 383 c.Assert(prevPage, qt.Not(qt.IsNil)) 384 c.Assert(prevPage.Kind(), qt.Equals, page.KindPage) 385 386 for { 387 if prevPage == nil { 388 break 389 } 390 c.Assert(prevPage.Kind(), qt.Equals, page.KindPage) 391 prevPage = prevPage.Prev() 392 } 393 394 // Check bundles 395 b.AssertFileContent("public/fr/bundles/b1/index.html", "RelPermalink: /blog/fr/bundles/b1/|") 396 bundleFr := frSite.getPage(page.KindPage, "bundles/b1/index.md") 397 c.Assert(bundleFr, qt.Not(qt.IsNil)) 398 c.Assert(len(bundleFr.Resources()), qt.Equals, 1) 399 logoFr := bundleFr.Resources().GetMatch("logo*") 400 c.Assert(logoFr, qt.Not(qt.IsNil)) 401 b.AssertFileContent("public/fr/bundles/b1/index.html", "Resources: image/png: /blog/fr/bundles/b1/logo.png") 402 b.AssertFileContent("public/fr/bundles/b1/logo.png", "PNG Data") 403 404 bundleEn := enSite.getPage(page.KindPage, "bundles/b1/index.en.md") 405 c.Assert(bundleEn, qt.Not(qt.IsNil)) 406 b.AssertFileContent("public/en/bundles/b1/index.html", "RelPermalink: /blog/en/bundles/b1/|") 407 c.Assert(len(bundleEn.Resources()), qt.Equals, 1) 408 logoEn := bundleEn.Resources().GetMatch("logo*") 409 c.Assert(logoEn, qt.Not(qt.IsNil)) 410 b.AssertFileContent("public/en/bundles/b1/index.html", "Resources: image/png: /blog/en/bundles/b1/logo.png") 411 b.AssertFileContent("public/en/bundles/b1/logo.png", "PNG Data") 412 } 413 414 func TestMultiSitesRebuild(t *testing.T) { 415 // t.Parallel() not supported, see https://github.com/fortytw2/leaktest/issues/4 416 // This leaktest seems to be a little bit shaky on Travis. 417 if !htesting.IsCI() { 418 defer leaktest.CheckTimeout(t, 10*time.Second)() 419 } 420 421 c := qt.New(t) 422 423 b := newMultiSiteTestDefaultBuilder(t).Running().CreateSites().Build(BuildCfg{}) 424 425 sites := b.H.Sites 426 fs := b.Fs 427 428 b.AssertFileContent("public/en/sect/doc2/index.html", "Single: doc2|Hello|en|", "\n\n<h1 id=\"doc2\">doc2</h1>\n\n<p><em>some content</em>") 429 430 enSite := sites[0] 431 frSite := sites[1] 432 433 c.Assert(len(enSite.RegularPages()), qt.Equals, 5) 434 c.Assert(len(frSite.RegularPages()), qt.Equals, 4) 435 436 // Verify translations 437 b.AssertFileContent("public/en/sect/doc1-slug/index.html", "Hello") 438 b.AssertFileContent("public/fr/sect/doc1/index.html", "Bonjour") 439 440 // check single page content 441 b.AssertFileContent("public/fr/sect/doc1/index.html", "Single", "Shortcode: Bonjour") 442 b.AssertFileContent("public/en/sect/doc1-slug/index.html", "Single", "Shortcode: Hello") 443 444 homeEn := enSite.getPage(page.KindHome) 445 c.Assert(homeEn, qt.Not(qt.IsNil)) 446 c.Assert(len(homeEn.Translations()), qt.Equals, 3) 447 448 contentFs := b.H.Fs.Source 449 450 for i, this := range []struct { 451 preFunc func(t *testing.T) 452 events []fsnotify.Event 453 assertFunc func(t *testing.T) 454 }{ 455 // * Remove doc 456 // * Add docs existing languages 457 // (Add doc new language: TODO(bep) we should load config.toml as part of these so we can add languages). 458 // * Rename file 459 // * Change doc 460 // * Change a template 461 // * Change language file 462 { 463 func(t *testing.T) { 464 fs.Source.Remove("content/sect/doc2.en.md") 465 }, 466 []fsnotify.Event{{Name: filepath.FromSlash("content/sect/doc2.en.md"), Op: fsnotify.Remove}}, 467 func(t *testing.T) { 468 c.Assert(len(enSite.RegularPages()), qt.Equals, 4, qt.Commentf("1 en removed")) 469 }, 470 }, 471 { 472 func(t *testing.T) { 473 writeNewContentFile(t, contentFs, "new_en_1", "2016-07-31", "content/new1.en.md", -5) 474 writeNewContentFile(t, contentFs, "new_en_2", "1989-07-30", "content/new2.en.md", -10) 475 writeNewContentFile(t, contentFs, "new_fr_1", "2016-07-30", "content/new1.fr.md", 10) 476 }, 477 []fsnotify.Event{ 478 {Name: filepath.FromSlash("content/new1.en.md"), Op: fsnotify.Create}, 479 {Name: filepath.FromSlash("content/new2.en.md"), Op: fsnotify.Create}, 480 {Name: filepath.FromSlash("content/new1.fr.md"), Op: fsnotify.Create}, 481 }, 482 func(t *testing.T) { 483 c.Assert(len(enSite.RegularPages()), qt.Equals, 6) 484 c.Assert(len(enSite.AllPages()), qt.Equals, 34) 485 c.Assert(len(frSite.RegularPages()), qt.Equals, 5) 486 c.Assert(frSite.RegularPages()[3].Title(), qt.Equals, "new_fr_1") 487 c.Assert(enSite.RegularPages()[0].Title(), qt.Equals, "new_en_2") 488 c.Assert(enSite.RegularPages()[1].Title(), qt.Equals, "new_en_1") 489 490 rendered := readDestination(t, fs, "public/en/new1/index.html") 491 c.Assert(strings.Contains(rendered, "new_en_1"), qt.Equals, true) 492 }, 493 }, 494 { 495 func(t *testing.T) { 496 p := "content/sect/doc1.en.md" 497 doc1 := readFileFromFs(t, contentFs, p) 498 doc1 += "CHANGED" 499 writeToFs(t, contentFs, p, doc1) 500 }, 501 []fsnotify.Event{{Name: filepath.FromSlash("content/sect/doc1.en.md"), Op: fsnotify.Write}}, 502 func(t *testing.T) { 503 c.Assert(len(enSite.RegularPages()), qt.Equals, 6) 504 doc1 := readDestination(t, fs, "public/en/sect/doc1-slug/index.html") 505 c.Assert(strings.Contains(doc1, "CHANGED"), qt.Equals, true) 506 }, 507 }, 508 // Rename a file 509 { 510 func(t *testing.T) { 511 if err := contentFs.Rename("content/new1.en.md", "content/new1renamed.en.md"); err != nil { 512 t.Fatalf("Rename failed: %s", err) 513 } 514 }, 515 []fsnotify.Event{ 516 {Name: filepath.FromSlash("content/new1renamed.en.md"), Op: fsnotify.Rename}, 517 {Name: filepath.FromSlash("content/new1.en.md"), Op: fsnotify.Rename}, 518 }, 519 func(t *testing.T) { 520 c.Assert(len(enSite.RegularPages()), qt.Equals, 6, qt.Commentf("Rename")) 521 c.Assert(enSite.RegularPages()[1].Title(), qt.Equals, "new_en_1") 522 rendered := readDestination(t, fs, "public/en/new1renamed/index.html") 523 c.Assert(rendered, qt.Contains, "new_en_1") 524 }, 525 }, 526 { 527 // Change a template 528 func(t *testing.T) { 529 template := "layouts/_default/single.html" 530 templateContent := readSource(t, fs, template) 531 templateContent += "{{ print \"Template Changed\"}}" 532 writeSource(t, fs, template, templateContent) 533 }, 534 []fsnotify.Event{{Name: filepath.FromSlash("layouts/_default/single.html"), Op: fsnotify.Write}}, 535 func(t *testing.T) { 536 c.Assert(len(enSite.RegularPages()), qt.Equals, 6) 537 c.Assert(len(enSite.AllPages()), qt.Equals, 34) 538 c.Assert(len(frSite.RegularPages()), qt.Equals, 5) 539 doc1 := readDestination(t, fs, "public/en/sect/doc1-slug/index.html") 540 c.Assert(strings.Contains(doc1, "Template Changed"), qt.Equals, true) 541 }, 542 }, 543 { 544 // Change a language file 545 func(t *testing.T) { 546 languageFile := "i18n/fr.yaml" 547 langContent := readSource(t, fs, languageFile) 548 langContent = strings.Replace(langContent, "Bonjour", "Salut", 1) 549 writeSource(t, fs, languageFile, langContent) 550 }, 551 []fsnotify.Event{{Name: filepath.FromSlash("i18n/fr.yaml"), Op: fsnotify.Write}}, 552 func(t *testing.T) { 553 c.Assert(len(enSite.RegularPages()), qt.Equals, 6) 554 c.Assert(len(enSite.AllPages()), qt.Equals, 34) 555 c.Assert(len(frSite.RegularPages()), qt.Equals, 5) 556 docEn := readDestination(t, fs, "public/en/sect/doc1-slug/index.html") 557 c.Assert(strings.Contains(docEn, "Hello"), qt.Equals, true) 558 docFr := readDestination(t, fs, "public/fr/sect/doc1/index.html") 559 c.Assert(strings.Contains(docFr, "Salut"), qt.Equals, true) 560 561 homeEn := enSite.getPage(page.KindHome) 562 c.Assert(homeEn, qt.Not(qt.IsNil)) 563 c.Assert(len(homeEn.Translations()), qt.Equals, 3) 564 c.Assert(homeEn.Translations()[0].Language().Lang, qt.Equals, "fr") 565 }, 566 }, 567 // Change a shortcode 568 { 569 func(t *testing.T) { 570 writeSource(t, fs, "layouts/shortcodes/shortcode.html", "Modified Shortcode: {{ i18n \"hello\" }}") 571 }, 572 []fsnotify.Event{ 573 {Name: filepath.FromSlash("layouts/shortcodes/shortcode.html"), Op: fsnotify.Write}, 574 }, 575 func(t *testing.T) { 576 c.Assert(len(enSite.RegularPages()), qt.Equals, 6) 577 c.Assert(len(enSite.AllPages()), qt.Equals, 34) 578 c.Assert(len(frSite.RegularPages()), qt.Equals, 5) 579 b.AssertFileContent("public/fr/sect/doc1/index.html", "Single", "Modified Shortcode: Salut") 580 b.AssertFileContent("public/en/sect/doc1-slug/index.html", "Single", "Modified Shortcode: Hello") 581 }, 582 }, 583 } { 584 585 if this.preFunc != nil { 586 this.preFunc(t) 587 } 588 589 err := b.H.Build(BuildCfg{}, this.events...) 590 if err != nil { 591 t.Fatalf("[%d] Failed to rebuild sites: %s", i, err) 592 } 593 594 this.assertFunc(t) 595 } 596 } 597 598 // https://github.com/gohugoio/hugo/issues/4706 599 func TestContentStressTest(t *testing.T) { 600 b := newTestSitesBuilder(t) 601 602 numPages := 500 603 604 contentTempl := ` 605 --- 606 %s 607 title: %q 608 weight: %d 609 multioutput: %t 610 --- 611 612 # Header 613 614 CONTENT 615 616 The End. 617 ` 618 619 contentTempl = strings.Replace(contentTempl, "CONTENT", strings.Repeat(` 620 621 ## Another header 622 623 Some text. Some more text. 624 625 `, 100), -1) 626 627 var content []string 628 defaultOutputs := `outputs: ["html", "json", "rss" ]` 629 630 for i := 1; i <= numPages; i++ { 631 outputs := defaultOutputs 632 multioutput := true 633 if i%3 == 0 { 634 outputs = `outputs: ["json"]` 635 multioutput = false 636 } 637 section := "s1" 638 if i%10 == 0 { 639 section = "s2" 640 } 641 content = append(content, []string{fmt.Sprintf("%s/page%d.md", section, i), fmt.Sprintf(contentTempl, outputs, fmt.Sprintf("Title %d", i), i, multioutput)}...) 642 } 643 644 content = append(content, []string{"_index.md", fmt.Sprintf(contentTempl, defaultOutputs, fmt.Sprintf("Home %d", 0), 0, true)}...) 645 content = append(content, []string{"s1/_index.md", fmt.Sprintf(contentTempl, defaultOutputs, fmt.Sprintf("S %d", 1), 1, true)}...) 646 content = append(content, []string{"s2/_index.md", fmt.Sprintf(contentTempl, defaultOutputs, fmt.Sprintf("S %d", 2), 2, true)}...) 647 648 b.WithSimpleConfigFile() 649 b.WithTemplates("layouts/_default/single.html", `Single: {{ .Content }}|RelPermalink: {{ .RelPermalink }}|Permalink: {{ .Permalink }}`) 650 b.WithTemplates("layouts/_default/myview.html", `View: {{ len .Content }}`) 651 b.WithTemplates("layouts/_default/single.json", `Single JSON: {{ .Content }}|RelPermalink: {{ .RelPermalink }}|Permalink: {{ .Permalink }}`) 652 b.WithTemplates("layouts/_default/list.html", ` 653 Page: {{ .Paginator.PageNumber }} 654 P: {{ with .File }}{{ path.Join .Path }}{{ end }} 655 List: {{ len .Paginator.Pages }}|List Content: {{ len .Content }} 656 {{ $shuffled := where .Site.RegularPages "Params.multioutput" true | shuffle }} 657 {{ $first5 := $shuffled | first 5 }} 658 L1: {{ len .Site.RegularPages }} L2: {{ len $first5 }} 659 {{ range $i, $e := $first5 }} 660 Render {{ $i }}: {{ .Render "myview" }} 661 {{ end }} 662 END 663 `) 664 665 b.WithContent(content...) 666 667 b.CreateSites().Build(BuildCfg{}) 668 669 contentMatchers := []string{"<h2 id=\"another-header\">Another header</h2>", "<h2 id=\"another-header-99\">Another header</h2>", "<p>The End.</p>"} 670 671 for i := 1; i <= numPages; i++ { 672 if i%3 != 0 { 673 section := "s1" 674 if i%10 == 0 { 675 section = "s2" 676 } 677 checkContent(b, fmt.Sprintf("public/%s/page%d/index.html", section, i), contentMatchers...) 678 } 679 } 680 681 for i := 1; i <= numPages; i++ { 682 section := "s1" 683 if i%10 == 0 { 684 section = "s2" 685 } 686 checkContent(b, fmt.Sprintf("public/%s/page%d/index.json", section, i), contentMatchers...) 687 } 688 689 checkContent(b, "public/s1/index.html", "P: s1/_index.md\nList: 10|List Content: 8132\n\n\nL1: 500 L2: 5\n\nRender 0: View: 8132\n\nRender 1: View: 8132\n\nRender 2: View: 8132\n\nRender 3: View: 8132\n\nRender 4: View: 8132\n\nEND\n") 690 checkContent(b, "public/s2/index.html", "P: s2/_index.md\nList: 10|List Content: 8132", "Render 4: View: 8132\n\nEND") 691 checkContent(b, "public/index.html", "P: _index.md\nList: 10|List Content: 8132", "4: View: 8132\n\nEND") 692 693 // Check paginated pages 694 for i := 2; i <= 9; i++ { 695 checkContent(b, fmt.Sprintf("public/page/%d/index.html", i), fmt.Sprintf("Page: %d", i), "Content: 8132\n\n\nL1: 500 L2: 5\n\nRender 0: View: 8132", "Render 4: View: 8132\n\nEND") 696 } 697 } 698 699 func checkContent(s *sitesBuilder, filename string, matches ...string) { 700 s.T.Helper() 701 content := readDestination(s.T, s.Fs, filename) 702 for _, match := range matches { 703 if !strings.Contains(content, match) { 704 s.Fatalf("No match for\n%q\nin content for %s\n%q\nDiff:\n%s", match, filename, content, htesting.DiffStrings(content, match)) 705 } 706 } 707 } 708 709 func TestTranslationsFromContentToNonContent(t *testing.T) { 710 b := newTestSitesBuilder(t) 711 b.WithConfigFile("toml", ` 712 713 baseURL = "http://example.com/" 714 715 defaultContentLanguage = "en" 716 717 [languages] 718 [languages.en] 719 weight = 10 720 contentDir = "content/en" 721 [languages.nn] 722 weight = 20 723 contentDir = "content/nn" 724 725 726 `) 727 728 b.WithContent("en/mysection/_index.md", ` 729 --- 730 Title: My Section 731 --- 732 733 `) 734 735 b.WithContent("en/_index.md", ` 736 --- 737 Title: My Home 738 --- 739 740 `) 741 742 b.WithContent("en/categories/mycat/_index.md", ` 743 --- 744 Title: My MyCat 745 --- 746 747 `) 748 749 b.WithContent("en/categories/_index.md", ` 750 --- 751 Title: My categories 752 --- 753 754 `) 755 756 for _, lang := range []string{"en", "nn"} { 757 b.WithContent(lang+"/mysection/page.md", ` 758 --- 759 Title: My Page 760 categories: ["mycat"] 761 --- 762 763 `) 764 } 765 766 b.Build(BuildCfg{}) 767 768 for _, path := range []string{ 769 "/", 770 "/mysection", 771 "/categories", 772 "/categories/mycat", 773 } { 774 t.Run(path, func(t *testing.T) { 775 c := qt.New(t) 776 777 s1, _ := b.H.Sites[0].getPageNew(nil, path) 778 s2, _ := b.H.Sites[1].getPageNew(nil, path) 779 780 c.Assert(s1, qt.Not(qt.IsNil)) 781 c.Assert(s2, qt.Not(qt.IsNil)) 782 783 c.Assert(len(s1.Translations()), qt.Equals, 1) 784 c.Assert(len(s2.Translations()), qt.Equals, 1) 785 c.Assert(s1.Translations()[0], qt.Equals, s2) 786 c.Assert(s2.Translations()[0], qt.Equals, s1) 787 788 m1 := s1.Translations().MergeByLanguage(s2.Translations()) 789 m2 := s2.Translations().MergeByLanguage(s1.Translations()) 790 791 c.Assert(len(m1), qt.Equals, 1) 792 c.Assert(len(m2), qt.Equals, 1) 793 }) 794 } 795 } 796 797 // https://github.com/gohugoio/hugo/issues/5777 798 func TestTableOfContentsInShortcodes(t *testing.T) { 799 t.Parallel() 800 801 b := newMultiSiteTestDefaultBuilder(t) 802 803 b.WithTemplatesAdded("layouts/shortcodes/toc.html", tocShortcode) 804 b.WithTemplatesAdded("layouts/shortcodes/wrapper.html", "{{ .Inner }}") 805 b.WithContent("post/simple.en.md", tocPageSimple) 806 b.WithContent("post/variants1.en.md", tocPageVariants1) 807 b.WithContent("post/variants2.en.md", tocPageVariants2) 808 809 b.WithContent("post/withSCInHeading.en.md", tocPageWithShortcodesInHeadings) 810 811 b.CreateSites().Build(BuildCfg{}) 812 813 b.AssertFileContent("public/en/post/simple/index.html", 814 tocPageSimpleExpected, 815 // Make sure it is inserted twice 816 `TOC1: <nav id="TableOfContents">`, 817 `TOC2: <nav id="TableOfContents">`, 818 ) 819 820 b.AssertFileContentFn("public/en/post/variants1/index.html", func(s string) bool { 821 return strings.Count(s, "TableOfContents") == 4 822 }) 823 b.AssertFileContentFn("public/en/post/variants2/index.html", func(s string) bool { 824 return strings.Count(s, "TableOfContents") == 6 825 }) 826 827 b.AssertFileContent("public/en/post/withSCInHeading/index.html", tocPageWithShortcodesInHeadingsExpected) 828 } 829 830 var tocShortcode = ` 831 TOC1: {{ .Page.TableOfContents }} 832 833 TOC2: {{ .Page.TableOfContents }} 834 ` 835 836 func TestSelfReferencedContentInShortcode(t *testing.T) { 837 t.Parallel() 838 839 b := newMultiSiteTestDefaultBuilder(t) 840 841 var ( 842 shortcode = `{{- .Page.Content -}}{{- .Page.Summary -}}{{- .Page.Plain -}}{{- .Page.PlainWords -}}{{- .Page.WordCount -}}{{- .Page.ReadingTime -}}` 843 844 page = `--- 845 title: sctest 846 --- 847 Empty:{{< mycontent >}}: 848 ` 849 ) 850 851 b.WithTemplatesAdded("layouts/shortcodes/mycontent.html", shortcode) 852 b.WithContent("post/simple.en.md", page) 853 854 b.CreateSites().Build(BuildCfg{}) 855 856 b.AssertFileContent("public/en/post/simple/index.html", "Empty:[]00:") 857 } 858 859 var tocPageSimple = `--- 860 title: tocTest 861 publishdate: "2000-01-01" 862 --- 863 {{< toc >}} 864 # Heading 1 {#1} 865 Some text. 866 ## Subheading 1.1 {#1-1} 867 Some more text. 868 # Heading 2 {#2} 869 Even more text. 870 ## Subheading 2.1 {#2-1} 871 Lorem ipsum... 872 ` 873 874 var tocPageVariants1 = `--- 875 title: tocTest 876 publishdate: "2000-01-01" 877 --- 878 Variant 1: 879 {{% wrapper %}} 880 {{< toc >}} 881 {{% /wrapper %}} 882 # Heading 1 883 884 Variant 3: 885 {{% toc %}} 886 887 ` 888 889 var tocPageVariants2 = `--- 890 title: tocTest 891 publishdate: "2000-01-01" 892 --- 893 Variant 1: 894 {{% wrapper %}} 895 {{< toc >}} 896 {{% /wrapper %}} 897 # Heading 1 898 899 Variant 2: 900 {{< wrapper >}} 901 {{< toc >}} 902 {{< /wrapper >}} 903 904 Variant 3: 905 {{% toc %}} 906 907 ` 908 909 var tocPageSimpleExpected = `<nav id="TableOfContents"> 910 <ul> 911 <li><a href="#1">Heading 1</a> 912 <ul> 913 <li><a href="#1-1">Subheading 1.1</a></li> 914 </ul></li> 915 <li><a href="#2">Heading 2</a> 916 <ul> 917 <li><a href="#2-1">Subheading 2.1</a></li> 918 </ul></li> 919 </ul> 920 </nav>` 921 922 var tocPageWithShortcodesInHeadings = `--- 923 title: tocTest 924 publishdate: "2000-01-01" 925 --- 926 927 {{< toc >}} 928 929 # Heading 1 {#1} 930 931 Some text. 932 933 ## Subheading 1.1 {{< shortcode >}} {#1-1} 934 935 Some more text. 936 937 # Heading 2 {{% shortcode %}} {#2} 938 939 Even more text. 940 941 ## Subheading 2.1 {#2-1} 942 943 Lorem ipsum... 944 ` 945 946 var tocPageWithShortcodesInHeadingsExpected = `<nav id="TableOfContents"> 947 <ul> 948 <li><a href="#1">Heading 1</a> 949 <ul> 950 <li><a href="#1-1">Subheading 1.1 Shortcode: Hello</a></li> 951 </ul></li> 952 <li><a href="#2">Heading 2 Shortcode: Hello</a> 953 <ul> 954 <li><a href="#2-1">Subheading 2.1</a></li> 955 </ul></li> 956 </ul> 957 </nav>` 958 959 var multiSiteTOMLConfigTemplate = ` 960 baseURL = "http://example.com/blog" 961 962 paginate = 1 963 disablePathToLower = true 964 defaultContentLanguage = "{{ .DefaultContentLanguage }}" 965 defaultContentLanguageInSubdir = {{ .DefaultContentLanguageInSubdir }} 966 enableRobotsTXT = true 967 968 [permalinks] 969 other = "/somewhere/else/:filename" 970 971 # TODO(bep) 972 [markup] 973 defaultMarkdownHandler = "blackfriday" 974 [markup.blackfriday] 975 angledQuotes = true 976 977 [Taxonomies] 978 tag = "tags" 979 980 [Languages] 981 [Languages.en] 982 weight = 10 983 title = "In English" 984 languageName = "English" 985 [Languages.en.blackfriday] 986 angledQuotes = false 987 [[Languages.en.menu.main]] 988 url = "/" 989 name = "Home" 990 weight = 0 991 992 [Languages.fr] 993 weight = 20 994 title = "Le Français" 995 languageName = "Français" 996 [Languages.fr.Taxonomies] 997 plaque = "plaques" 998 999 [Languages.nn] 1000 weight = 30 1001 title = "På nynorsk" 1002 languageName = "Nynorsk" 1003 paginatePath = "side" 1004 [Languages.nn.Taxonomies] 1005 lag = "lag" 1006 [[Languages.nn.menu.main]] 1007 url = "/" 1008 name = "Heim" 1009 weight = 1 1010 1011 [Languages.nb] 1012 weight = 40 1013 title = "På bokmål" 1014 languageName = "Bokmål" 1015 paginatePath = "side" 1016 [Languages.nb.Taxonomies] 1017 lag = "lag" 1018 ` 1019 1020 var multiSiteYAMLConfigTemplate = ` 1021 baseURL: "http://example.com/blog" 1022 1023 disablePathToLower: true 1024 paginate: 1 1025 defaultContentLanguage: "{{ .DefaultContentLanguage }}" 1026 defaultContentLanguageInSubdir: {{ .DefaultContentLanguageInSubdir }} 1027 enableRobotsTXT: true 1028 1029 permalinks: 1030 other: "/somewhere/else/:filename" 1031 1032 # TODO(bep) 1033 markup: 1034 defaultMarkdownHandler: blackfriday 1035 blackFriday: 1036 angledQuotes: true 1037 1038 Taxonomies: 1039 tag: "tags" 1040 1041 Languages: 1042 en: 1043 weight: 10 1044 title: "In English" 1045 languageName: "English" 1046 blackfriday: 1047 angledQuotes: false 1048 menu: 1049 main: 1050 - url: "/" 1051 name: "Home" 1052 weight: 0 1053 fr: 1054 weight: 20 1055 title: "Le Français" 1056 languageName: "Français" 1057 Taxonomies: 1058 plaque: "plaques" 1059 nn: 1060 weight: 30 1061 title: "På nynorsk" 1062 languageName: "Nynorsk" 1063 paginatePath: "side" 1064 Taxonomies: 1065 lag: "lag" 1066 menu: 1067 main: 1068 - url: "/" 1069 name: "Heim" 1070 weight: 1 1071 nb: 1072 weight: 40 1073 title: "På bokmål" 1074 languageName: "Bokmål" 1075 paginatePath: "side" 1076 Taxonomies: 1077 lag: "lag" 1078 1079 ` 1080 1081 // TODO(bep) clean move 1082 var multiSiteJSONConfigTemplate = ` 1083 { 1084 "baseURL": "http://example.com/blog", 1085 "paginate": 1, 1086 "disablePathToLower": true, 1087 "defaultContentLanguage": "{{ .DefaultContentLanguage }}", 1088 "defaultContentLanguageInSubdir": true, 1089 "enableRobotsTXT": true, 1090 "permalinks": { 1091 "other": "/somewhere/else/:filename" 1092 }, 1093 "markup": { 1094 "defaultMarkdownHandler": "blackfriday", 1095 "blackfriday": { 1096 "angledQuotes": true 1097 } 1098 }, 1099 "Taxonomies": { 1100 "tag": "tags" 1101 }, 1102 "Languages": { 1103 "en": { 1104 "weight": 10, 1105 "title": "In English", 1106 "languageName": "English", 1107 "blackfriday": { 1108 "angledQuotes": false 1109 }, 1110 "menu": { 1111 "main": [ 1112 { 1113 "url": "/", 1114 "name": "Home", 1115 "weight": 0 1116 } 1117 ] 1118 } 1119 }, 1120 "fr": { 1121 "weight": 20, 1122 "title": "Le Français", 1123 "languageName": "Français", 1124 "Taxonomies": { 1125 "plaque": "plaques" 1126 } 1127 }, 1128 "nn": { 1129 "weight": 30, 1130 "title": "På nynorsk", 1131 "paginatePath": "side", 1132 "languageName": "Nynorsk", 1133 "Taxonomies": { 1134 "lag": "lag" 1135 }, 1136 "menu": { 1137 "main": [ 1138 { 1139 "url": "/", 1140 "name": "Heim", 1141 "weight": 1 1142 } 1143 ] 1144 } 1145 }, 1146 "nb": { 1147 "weight": 40, 1148 "title": "På bokmål", 1149 "paginatePath": "side", 1150 "languageName": "Bokmål", 1151 "Taxonomies": { 1152 "lag": "lag" 1153 } 1154 } 1155 } 1156 } 1157 ` 1158 1159 func writeSource(t testing.TB, fs *hugofs.Fs, filename, content string) { 1160 t.Helper() 1161 writeToFs(t, fs.Source, filename, content) 1162 } 1163 1164 func writeToFs(t testing.TB, fs afero.Fs, filename, content string) { 1165 t.Helper() 1166 if err := afero.WriteFile(fs, filepath.FromSlash(filename), []byte(content), 0755); err != nil { 1167 t.Fatalf("Failed to write file: %s", err) 1168 } 1169 } 1170 1171 func readDestination(t testing.TB, fs *hugofs.Fs, filename string) string { 1172 t.Helper() 1173 return readFileFromFs(t, fs.Destination, filename) 1174 } 1175 1176 func destinationExists(fs *hugofs.Fs, filename string) bool { 1177 b, err := helpers.Exists(filename, fs.Destination) 1178 if err != nil { 1179 panic(err) 1180 } 1181 return b 1182 } 1183 1184 func readSource(t *testing.T, fs *hugofs.Fs, filename string) string { 1185 return readFileFromFs(t, fs.Source, filename) 1186 } 1187 1188 func readFileFromFs(t testing.TB, fs afero.Fs, filename string) string { 1189 t.Helper() 1190 filename = filepath.Clean(filename) 1191 b, err := afero.ReadFile(fs, filename) 1192 if err != nil { 1193 // Print some debug info 1194 hadSlash := strings.HasPrefix(filename, helpers.FilePathSeparator) 1195 start := 0 1196 if hadSlash { 1197 start = 1 1198 } 1199 end := start + 1 1200 1201 parts := strings.Split(filename, helpers.FilePathSeparator) 1202 if parts[start] == "work" { 1203 end++ 1204 } 1205 1206 /* 1207 root := filepath.Join(parts[start:end]...) 1208 if hadSlash { 1209 root = helpers.FilePathSeparator + root 1210 } 1211 1212 helpers.PrintFs(fs, root, os.Stdout) 1213 */ 1214 1215 t.Fatalf("Failed to read file: %s", err) 1216 } 1217 return string(b) 1218 } 1219 1220 const testPageTemplate = `--- 1221 title: "%s" 1222 publishdate: "%s" 1223 weight: %d 1224 --- 1225 # Doc %s 1226 ` 1227 1228 func newTestPage(title, date string, weight int) string { 1229 return fmt.Sprintf(testPageTemplate, title, date, weight, title) 1230 } 1231 1232 func writeNewContentFile(t *testing.T, fs afero.Fs, title, date, filename string, weight int) { 1233 content := newTestPage(title, date, weight) 1234 writeToFs(t, fs, filename, content) 1235 } 1236 1237 type multiSiteTestBuilder struct { 1238 configData interface{} 1239 config string 1240 configFormat string 1241 1242 *sitesBuilder 1243 } 1244 1245 func newMultiSiteTestDefaultBuilder(t testing.TB) *multiSiteTestBuilder { 1246 return newMultiSiteTestBuilder(t, "", "", nil) 1247 } 1248 1249 func (b *multiSiteTestBuilder) WithNewConfig(config string) *multiSiteTestBuilder { 1250 b.WithConfigTemplate(b.configData, b.configFormat, config) 1251 return b 1252 } 1253 1254 func (b *multiSiteTestBuilder) WithNewConfigData(data interface{}) *multiSiteTestBuilder { 1255 b.WithConfigTemplate(data, b.configFormat, b.config) 1256 return b 1257 } 1258 1259 func newMultiSiteTestBuilder(t testing.TB, configFormat, config string, configData interface{}) *multiSiteTestBuilder { 1260 if configData == nil { 1261 configData = map[string]interface{}{ 1262 "DefaultContentLanguage": "fr", 1263 "DefaultContentLanguageInSubdir": true, 1264 } 1265 } 1266 1267 if config == "" { 1268 config = multiSiteTOMLConfigTemplate 1269 } 1270 1271 if configFormat == "" { 1272 configFormat = "toml" 1273 } 1274 1275 b := newTestSitesBuilder(t).WithConfigTemplate(configData, configFormat, config) 1276 b.WithContent("root.en.md", `--- 1277 title: root 1278 weight: 10000 1279 slug: root 1280 publishdate: "2000-01-01" 1281 --- 1282 # root 1283 `, 1284 "sect/doc1.en.md", `--- 1285 title: doc1 1286 weight: 1 1287 slug: doc1-slug 1288 tags: 1289 - tag1 1290 publishdate: "2000-01-01" 1291 --- 1292 # doc1 1293 *some "content"* 1294 1295 {{< shortcode >}} 1296 1297 {{< lingo >}} 1298 1299 NOTE: slug should be used as URL 1300 `, 1301 "sect/doc1.fr.md", `--- 1302 title: doc1 1303 weight: 1 1304 plaques: 1305 - FRtag1 1306 - FRtag2 1307 publishdate: "2000-01-04" 1308 --- 1309 # doc1 1310 *quelque "contenu"* 1311 1312 {{< shortcode >}} 1313 1314 {{< lingo >}} 1315 1316 NOTE: should be in the 'en' Page's 'Translations' field. 1317 NOTE: date is after "doc3" 1318 `, 1319 "sect/doc2.en.md", `--- 1320 title: doc2 1321 weight: 2 1322 publishdate: "2000-01-02" 1323 --- 1324 # doc2 1325 *some content* 1326 NOTE: without slug, "doc2" should be used, without ".en" as URL 1327 `, 1328 "sect/doc3.en.md", `--- 1329 title: doc3 1330 weight: 3 1331 publishdate: "2000-01-03" 1332 aliases: [/en/al/alias1,/al/alias2/] 1333 tags: 1334 - tag2 1335 - tag1 1336 url: /superbob/ 1337 --- 1338 # doc3 1339 *some content* 1340 NOTE: third 'en' doc, should trigger pagination on home page. 1341 `, 1342 "sect/doc4.md", `--- 1343 title: doc4 1344 weight: 4 1345 plaques: 1346 - FRtag1 1347 publishdate: "2000-01-05" 1348 --- 1349 # doc4 1350 *du contenu francophone* 1351 NOTE: should use the defaultContentLanguage and mark this doc as 'fr'. 1352 NOTE: doesn't have any corresponding translation in 'en' 1353 `, 1354 "other/doc5.fr.md", `--- 1355 title: doc5 1356 weight: 5 1357 publishdate: "2000-01-06" 1358 --- 1359 # doc5 1360 *autre contenu francophone* 1361 NOTE: should use the "permalinks" configuration with :filename 1362 `, 1363 // Add some for the stats 1364 "stats/expired.fr.md", `--- 1365 title: expired 1366 publishdate: "2000-01-06" 1367 expiryDate: "2001-01-06" 1368 --- 1369 # Expired 1370 `, 1371 "stats/future.fr.md", `--- 1372 title: future 1373 weight: 6 1374 publishdate: "2100-01-06" 1375 --- 1376 # Future 1377 `, 1378 "stats/expired.en.md", `--- 1379 title: expired 1380 weight: 7 1381 publishdate: "2000-01-06" 1382 expiryDate: "2001-01-06" 1383 --- 1384 # Expired 1385 `, 1386 "stats/future.en.md", `--- 1387 title: future 1388 weight: 6 1389 publishdate: "2100-01-06" 1390 --- 1391 # Future 1392 `, 1393 "stats/draft.en.md", `--- 1394 title: expired 1395 publishdate: "2000-01-06" 1396 draft: true 1397 --- 1398 # Draft 1399 `, 1400 "stats/tax.nn.md", `--- 1401 title: Tax NN 1402 weight: 8 1403 publishdate: "2000-01-06" 1404 weight: 1001 1405 lag: 1406 - Sogndal 1407 --- 1408 # Tax NN 1409 `, 1410 "stats/tax.nb.md", `--- 1411 title: Tax NB 1412 weight: 8 1413 publishdate: "2000-01-06" 1414 weight: 1002 1415 lag: 1416 - Sogndal 1417 --- 1418 # Tax NB 1419 `, 1420 // Bundle 1421 "bundles/b1/index.en.md", `--- 1422 title: Bundle EN 1423 publishdate: "2000-01-06" 1424 weight: 2001 1425 --- 1426 # Bundle Content EN 1427 `, 1428 "bundles/b1/index.md", `--- 1429 title: Bundle Default 1430 publishdate: "2000-01-06" 1431 weight: 2002 1432 --- 1433 # Bundle Content Default 1434 `, 1435 "bundles/b1/logo.png", ` 1436 PNG Data 1437 `) 1438 1439 i18nContent := func(id, value string) string { 1440 return fmt.Sprintf(` 1441 [%s] 1442 other = %q 1443 `, id, value) 1444 } 1445 1446 b.WithSourceFile("i18n/en.toml", i18nContent("hello", "Hello")) 1447 b.WithSourceFile("i18n/fr.toml", i18nContent("hello", "Bonjour")) 1448 b.WithSourceFile("i18n/nb.toml", i18nContent("hello", "Hallo")) 1449 b.WithSourceFile("i18n/nn.toml", i18nContent("hello", "Hallo")) 1450 1451 return &multiSiteTestBuilder{sitesBuilder: b, configFormat: configFormat, config: config, configData: configData} 1452 } 1453 1454 func TestRebuildOnAssetChange(t *testing.T) { 1455 b := newTestSitesBuilder(t).Running() 1456 b.WithTemplatesAdded("index.html", ` 1457 {{ (resources.Get "data.json").Content }} 1458 `) 1459 b.WithSourceFile("assets/data.json", "orig data") 1460 1461 b.Build(BuildCfg{}) 1462 b.AssertFileContent("public/index.html", `orig data`) 1463 1464 b.EditFiles("assets/data.json", "changed data") 1465 1466 b.Build(BuildCfg{}) 1467 b.AssertFileContent("public/index.html", `changed data`) 1468 }