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