github.com/graemephi/kahugo@v0.62.3-0.20211121071557-d78c0423784d/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), "&laquo;"), qt.Equals, true)
   370  	c.Assert(strings.Contains(content(doc1en), "&laquo;"), qt.Equals, false)
   371  	c.Assert(strings.Contains(content(doc1en), "&ldquo;"), 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  }