ref: 956a2dce8d9fd51f6c4f827bd2ae859a6f22ac6f
dir: /hugolib/hugo_sites_build_test.go/
package hugolib import ( "bytes" "fmt" "strings" "testing" "html/template" "os" "path/filepath" "time" "github.com/fortytw2/leaktest" "github.com/fsnotify/fsnotify" "github.com/gohugoio/hugo/deps" "github.com/gohugoio/hugo/helpers" "github.com/gohugoio/hugo/hugofs" "github.com/spf13/afero" "github.com/spf13/viper" "github.com/stretchr/testify/require" ) type testSiteConfig struct { DefaultContentLanguage string DefaultContentLanguageInSubdir bool Fs afero.Fs Running bool } func TestMultiSitesMainLangInRoot(t *testing.T) { t.Parallel() for _, b := range []bool{true, false} { doTestMultiSitesMainLangInRoot(t, b) } } func doTestMultiSitesMainLangInRoot(t *testing.T, defaultInSubDir bool) { siteConfig := testSiteConfig{Fs: afero.NewMemMapFs(), DefaultContentLanguage: "fr", DefaultContentLanguageInSubdir: defaultInSubDir} sites := createMultiTestSites(t, siteConfig, multiSiteTOMLConfigTemplate) fs := sites.Fs th := testHelper{sites.Cfg, fs, t} err := sites.Build(BuildCfg{}) if err != nil { t.Fatalf("Failed to build sites: %s", err) } require.Len(t, sites.Sites, 4) enSite := sites.Sites[0] frSite := sites.Sites[1] require.Equal(t, "/en", enSite.Info.LanguagePrefix) if defaultInSubDir { require.Equal(t, "/fr", frSite.Info.LanguagePrefix) } else { require.Equal(t, "", frSite.Info.LanguagePrefix) } require.Equal(t, "/blog/en/foo", enSite.PathSpec.RelURL("foo", true)) doc1en := enSite.RegularPages[0] doc1fr := frSite.RegularPages[0] enPerm := doc1en.Permalink() enRelPerm := doc1en.RelPermalink() require.Equal(t, "http://example.com/blog/en/sect/doc1-slug/", enPerm) require.Equal(t, "/blog/en/sect/doc1-slug/", enRelPerm) frPerm := doc1fr.Permalink() frRelPerm := doc1fr.RelPermalink() // Main language in root require.Equal(t, th.replaceDefaultContentLanguageValue("http://example.com/blog/fr/sect/doc1/"), frPerm) require.Equal(t, th.replaceDefaultContentLanguageValue("/blog/fr/sect/doc1/"), frRelPerm) th.assertFileContent("public/fr/sect/doc1/index.html", "Single", "Bonjour") th.assertFileContent("public/en/sect/doc1-slug/index.html", "Single", "Hello") // Check home if defaultInSubDir { // should have a redirect on top level. th.assertFileContentStraight("public/index.html", `<meta http-equiv="refresh" content="0; url=http://example.com/blog/fr" />`) } else { // should have redirect back to root th.assertFileContentStraight("public/fr/index.html", `<meta http-equiv="refresh" content="0; url=http://example.com/blog" />`) } th.assertFileContent("public/fr/index.html", "Home", "Bonjour") th.assertFileContent("public/en/index.html", "Home", "Hello") // Check list pages th.assertFileContent("public/fr/sect/index.html", "List", "Bonjour") th.assertFileContent("public/en/sect/index.html", "List", "Hello") th.assertFileContent("public/fr/plaques/frtag1/index.html", "List", "Bonjour") th.assertFileContent("public/en/tags/tag1/index.html", "List", "Hello") // Check sitemaps // Sitemaps behaves different: In a multilanguage setup there will always be a index file and // one sitemap in each lang folder. th.assertFileContentStraight("public/sitemap.xml", "<loc>http://example.com/blog/en/sitemap.xml</loc>", "<loc>http://example.com/blog/fr/sitemap.xml</loc>") if defaultInSubDir { th.assertFileContentStraight("public/fr/sitemap.xml", "<loc>http://example.com/blog/fr/</loc>") } else { th.assertFileContentStraight("public/fr/sitemap.xml", "<loc>http://example.com/blog/</loc>") } th.assertFileContent("public/en/sitemap.xml", "<loc>http://example.com/blog/en/</loc>") // Check rss th.assertFileContent("public/fr/index.xml", `<atom:link href="http://example.com/blog/fr/index.xml"`, `rel="self" type="application/rss+xml"`) th.assertFileContent("public/en/index.xml", `<atom:link href="http://example.com/blog/en/index.xml"`) th.assertFileContent("public/fr/sect/index.xml", `<atom:link href="http://example.com/blog/fr/sect/index.xml"`) th.assertFileContent("public/en/sect/index.xml", `<atom:link href="http://example.com/blog/en/sect/index.xml"`) th.assertFileContent("public/fr/plaques/frtag1/index.xml", `<atom:link href="http://example.com/blog/fr/plaques/frtag1/index.xml"`) th.assertFileContent("public/en/tags/tag1/index.xml", `<atom:link href="http://example.com/blog/en/tags/tag1/index.xml"`) // Check paginators th.assertFileContent("public/fr/page/1/index.html", `refresh" content="0; url=http://example.com/blog/fr/"`) th.assertFileContent("public/en/page/1/index.html", `refresh" content="0; url=http://example.com/blog/en/"`) th.assertFileContent("public/fr/page/2/index.html", "Home Page 2", "Bonjour", "http://example.com/blog/fr/") th.assertFileContent("public/en/page/2/index.html", "Home Page 2", "Hello", "http://example.com/blog/en/") th.assertFileContent("public/fr/sect/page/1/index.html", `refresh" content="0; url=http://example.com/blog/fr/sect/"`) th.assertFileContent("public/en/sect/page/1/index.html", `refresh" content="0; url=http://example.com/blog/en/sect/"`) th.assertFileContent("public/fr/sect/page/2/index.html", "List Page 2", "Bonjour", "http://example.com/blog/fr/sect/") th.assertFileContent("public/en/sect/page/2/index.html", "List Page 2", "Hello", "http://example.com/blog/en/sect/") th.assertFileContent("public/fr/plaques/frtag1/page/1/index.html", `refresh" content="0; url=http://example.com/blog/fr/plaques/frtag1/"`) th.assertFileContent("public/en/tags/tag1/page/1/index.html", `refresh" content="0; url=http://example.com/blog/en/tags/tag1/"`) th.assertFileContent("public/fr/plaques/frtag1/page/2/index.html", "List Page 2", "Bonjour", "http://example.com/blog/fr/plaques/frtag1/") th.assertFileContent("public/en/tags/tag1/page/2/index.html", "List Page 2", "Hello", "http://example.com/blog/en/tags/tag1/") // nn (Nynorsk) and nb (Bokmål) have custom pagePath: side ("page" in Norwegian) th.assertFileContent("public/nn/side/1/index.html", `refresh" content="0; url=http://example.com/blog/nn/"`) th.assertFileContent("public/nb/side/1/index.html", `refresh" content="0; url=http://example.com/blog/nb/"`) } func TestMultiSitesWithTwoLanguages(t *testing.T) { t.Parallel() assert := require.New(t) mm := afero.NewMemMapFs() writeToFs(t, mm, "config.toml", ` defaultContentLanguage = "nn" [languages] [languages.nn] languageName = "Nynorsk" weight = 1 title = "Tittel på Nynorsk" [languages.nn.params] p1 = "p1nn" [languages.en] title = "Title in English" languageName = "English" weight = 2 [languages.en.params] p1 = "p1en" `, ) cfg, err := LoadConfig(mm, "", "config.toml") require.NoError(t, err) fs := hugofs.NewFrom(mm, cfg) sites, err := NewHugoSites(deps.DepsCfg{Fs: fs, Cfg: cfg}) if err != nil { t.Fatalf("Failed to create sites: %s", err) } writeSource(t, fs, filepath.Join("content", "foo.md"), "foo") // Add some data writeSource(t, fs, filepath.Join("data", "hugo.toml"), "slogan = \"Hugo Rocks!\"") assert.NoError(sites.Build(BuildCfg{})) assert.Len(sites.Sites, 2) nnSite := sites.Sites[0] nnHome := nnSite.getPage(KindHome) assert.Len(nnHome.AllTranslations(), 2) assert.Len(nnHome.Translations(), 1) assert.True(nnHome.IsTranslated()) enHome := sites.Sites[1].getPage(KindHome) p1, err := enHome.Param("p1") assert.NoError(err) assert.Equal("p1en", p1) p1, err = nnHome.Param("p1") assert.NoError(err) assert.Equal("p1nn", p1) } // func TestMultiSitesBuild(t *testing.T) { t.Parallel() for _, config := range []struct { content string suffix string }{ {multiSiteTOMLConfigTemplate, "toml"}, {multiSiteYAMLConfigTemplate, "yml"}, {multiSiteJSONConfigTemplate, "json"}, } { doTestMultiSitesBuild(t, config.content, config.suffix) } } func doTestMultiSitesBuild(t *testing.T, configTemplate, configSuffix string) { siteConfig := testSiteConfig{Fs: afero.NewMemMapFs(), DefaultContentLanguage: "fr", DefaultContentLanguageInSubdir: true} sites := createMultiTestSitesForConfig(t, siteConfig, configTemplate, configSuffix) require.Len(t, sites.Sites, 4) fs := sites.Fs th := testHelper{sites.Cfg, fs, t} err := sites.Build(BuildCfg{}) if err != nil { t.Fatalf("Failed to build sites: %s", err) } // Check site config for _, s := range sites.Sites { require.True(t, s.Info.defaultContentLanguageInSubdir, s.Info.Title) require.NotNil(t, s.disabledKinds) } gp1 := sites.GetContentPage(filepath.FromSlash("content/sect/doc1.en.md")) require.NotNil(t, gp1) require.Equal(t, "doc1", gp1.title) gp2 := sites.GetContentPage(filepath.FromSlash("content/dummysect/notfound.md")) require.Nil(t, gp2) enSite := sites.Sites[0] enSiteHome := enSite.getPage(KindHome) require.True(t, enSiteHome.IsTranslated()) require.Equal(t, "en", enSite.Language.Lang) if len(enSite.RegularPages) != 5 { t.Fatal("Expected 5 english pages") } require.Len(t, enSite.AllPages, 32, "should have 32 total pages (including translations and index types)") doc1en := enSite.RegularPages[0] permalink := doc1en.Permalink() require.NoError(t, err, "permalink call failed") require.Equal(t, "http://example.com/blog/en/sect/doc1-slug/", permalink, "invalid doc1.en permalink") require.Len(t, doc1en.Translations(), 1, "doc1-en should have one translation, excluding itself") doc2 := enSite.RegularPages[1] permalink = doc2.Permalink() require.NoError(t, err, "permalink call failed") require.Equal(t, "http://example.com/blog/en/sect/doc2/", permalink, "invalid doc2 permalink") doc3 := enSite.RegularPages[2] permalink = doc3.Permalink() // Note that /superbob is a custom URL set in frontmatter. // We respect that URL literally (it can be /search.json) // and do no not do any language code prefixing. require.Equal(t, "http://example.com/blog/superbob/", permalink, "invalid doc3 permalink") require.Equal(t, "/superbob", doc3.URL(), "invalid url, was specified on doc3") th.assertFileContent("public/superbob/index.html", "doc3|Hello|en") require.Equal(t, doc2.Next, doc3, "doc3 should follow doc2, in .Next") doc1fr := doc1en.Translations()[0] permalink = doc1fr.Permalink() require.NoError(t, err, "permalink call failed") require.Equal(t, "http://example.com/blog/fr/sect/doc1/", permalink, "invalid doc1fr permalink") require.Equal(t, doc1en.Translations()[0], doc1fr, "doc1-en should have doc1-fr as translation") require.Equal(t, doc1fr.Translations()[0], doc1en, "doc1-fr should have doc1-en as translation") require.Equal(t, "fr", doc1fr.Language().Lang) doc4 := enSite.AllPages[4] permalink = doc4.Permalink() require.Equal(t, "http://example.com/blog/fr/sect/doc4/", permalink, "invalid doc4 permalink") require.Equal(t, "/blog/fr/sect/doc4/", doc4.URL()) require.Len(t, doc4.Translations(), 0, "found translations for doc4") doc5 := enSite.AllPages[5] permalink = doc5.Permalink() require.Equal(t, "http://example.com/blog/fr/somewhere/else/doc5/", permalink, "invalid doc5 permalink") // Taxonomies and their URLs require.Len(t, enSite.Taxonomies, 1, "should have 1 taxonomy") tags := enSite.Taxonomies["tags"] require.Len(t, tags, 2, "should have 2 different tags") require.Equal(t, tags["tag1"][0].Page, doc1en, "first tag1 page should be doc1") frSite := sites.Sites[1] require.Equal(t, "fr", frSite.Language.Lang) require.Len(t, frSite.RegularPages, 4, "should have 3 pages") require.Len(t, frSite.AllPages, 32, "should have 32 total pages (including translations and nodes)") for _, frenchPage := range frSite.RegularPages { require.Equal(t, "fr", frenchPage.Lang()) } // See https://github.com/gohugoio/hugo/issues/4285 // Before Hugo 0.33 you had to be explicit with the content path to get the correct Page, which // isn't ideal in a multilingual setup. You want a way to get the current language version if available. // Now you can do lookups with translation base name to get that behaviour. // Let us test all the regular page variants: getPageDoc1En := enSite.getPage(KindPage, filepath.ToSlash(doc1en.Path())) getPageDoc1EnBase := enSite.getPage(KindPage, "sect/doc1") getPageDoc1Fr := frSite.getPage(KindPage, filepath.ToSlash(doc1fr.Path())) getPageDoc1FrBase := frSite.getPage(KindPage, "sect/doc1") require.Equal(t, doc1en, getPageDoc1En) require.Equal(t, doc1fr, getPageDoc1Fr) require.Equal(t, doc1en, getPageDoc1EnBase) require.Equal(t, doc1fr, getPageDoc1FrBase) // Check redirect to main language, French languageRedirect := readDestination(t, fs, "public/index.html") require.True(t, strings.Contains(languageRedirect, "0; url=http://example.com/blog/fr"), languageRedirect) // check home page content (including data files rendering) th.assertFileContent("public/en/index.html", "Default Home Page 1", "Hello", "Hugo Rocks!") th.assertFileContent("public/fr/index.html", "French Home Page 1", "Bonjour", "Hugo Rocks!") // check single page content th.assertFileContent("public/fr/sect/doc1/index.html", "Single", "Shortcode: Bonjour", "LingoFrench") th.assertFileContent("public/en/sect/doc1-slug/index.html", "Single", "Shortcode: Hello", "LingoDefault") // Check node translations homeEn := enSite.getPage(KindHome) require.NotNil(t, homeEn) require.Len(t, homeEn.Translations(), 3) require.Equal(t, "fr", homeEn.Translations()[0].Lang()) require.Equal(t, "nn", homeEn.Translations()[1].Lang()) require.Equal(t, "På nynorsk", homeEn.Translations()[1].title) require.Equal(t, "nb", homeEn.Translations()[2].Lang()) require.Equal(t, "På bokmål", homeEn.Translations()[2].title, configSuffix) require.Equal(t, "Bokmål", homeEn.Translations()[2].Language().LanguageName, configSuffix) sectFr := frSite.getPage(KindSection, "sect") require.NotNil(t, sectFr) require.Equal(t, "fr", sectFr.Lang()) require.Len(t, sectFr.Translations(), 1) require.Equal(t, "en", sectFr.Translations()[0].Lang()) require.Equal(t, "Sects", sectFr.Translations()[0].title) nnSite := sites.Sites[2] require.Equal(t, "nn", nnSite.Language.Lang) taxNn := nnSite.getPage(KindTaxonomyTerm, "lag") require.NotNil(t, taxNn) require.Len(t, taxNn.Translations(), 1) require.Equal(t, "nb", taxNn.Translations()[0].Lang()) taxTermNn := nnSite.getPage(KindTaxonomy, "lag", "sogndal") require.NotNil(t, taxTermNn) require.Len(t, taxTermNn.Translations(), 1) require.Equal(t, "nb", taxTermNn.Translations()[0].Lang()) // Check sitemap(s) sitemapIndex := readDestination(t, fs, "public/sitemap.xml") require.True(t, strings.Contains(sitemapIndex, "<loc>http://example.com/blog/en/sitemap.xml</loc>"), sitemapIndex) require.True(t, strings.Contains(sitemapIndex, "<loc>http://example.com/blog/fr/sitemap.xml</loc>"), sitemapIndex) sitemapEn := readDestination(t, fs, "public/en/sitemap.xml") sitemapFr := readDestination(t, fs, "public/fr/sitemap.xml") require.True(t, strings.Contains(sitemapEn, "http://example.com/blog/en/sect/doc2/"), sitemapEn) require.True(t, strings.Contains(sitemapFr, "http://example.com/blog/fr/sect/doc1/"), sitemapFr) // Check taxonomies enTags := enSite.Taxonomies["tags"] frTags := frSite.Taxonomies["plaques"] require.Len(t, enTags, 2, fmt.Sprintf("Tags in en: %v", enTags)) require.Len(t, frTags, 2, fmt.Sprintf("Tags in fr: %v", frTags)) require.NotNil(t, enTags["tag1"]) require.NotNil(t, frTags["frtag1"]) readDestination(t, fs, "public/fr/plaques/frtag1/index.html") readDestination(t, fs, "public/en/tags/tag1/index.html") // Check Blackfriday config require.True(t, strings.Contains(string(doc1fr.Content), "«"), string(doc1fr.Content)) require.False(t, strings.Contains(string(doc1en.Content), "«"), string(doc1en.Content)) require.True(t, strings.Contains(string(doc1en.Content), "“"), string(doc1en.Content)) // Check that the drafts etc. are not built/processed/rendered. assertShouldNotBuild(t, sites) // en and nn have custom site menus require.Len(t, frSite.Menus, 0, "fr: "+configSuffix) require.Len(t, enSite.Menus, 1, "en: "+configSuffix) require.Len(t, nnSite.Menus, 1, "nn: "+configSuffix) require.Equal(t, "Home", enSite.Menus["main"].ByName()[0].Name) require.Equal(t, "Heim", nnSite.Menus["main"].ByName()[0].Name) // Issue #1302 require.Equal(t, template.URL(""), enSite.RegularPages[0].RSSLink()) // Issue #3108 next := enSite.RegularPages[0].Next require.NotNil(t, next) require.Equal(t, KindPage, next.Kind) for { if next == nil { break } require.Equal(t, KindPage, next.Kind) next = next.Next } // Check bundles bundleFr := frSite.getPage(KindPage, "bundles/b1/index.md") require.NotNil(t, bundleFr) require.Equal(t, "/blog/fr/bundles/b1/", bundleFr.RelPermalink()) require.Equal(t, 1, len(bundleFr.Resources)) logoFr := bundleFr.Resources.GetByPrefix("logo") require.NotNil(t, logoFr) require.Equal(t, "/blog/fr/bundles/b1/logo.png", logoFr.RelPermalink()) require.Contains(t, readFileFromFs(t, fs.Destination, filepath.FromSlash("public/fr/bundles/b1/logo.png")), "PNG Data") bundleEn := enSite.getPage(KindPage, "bundles/b1/index.en.md") require.NotNil(t, bundleEn) require.Equal(t, "/blog/en/bundles/b1/", bundleEn.RelPermalink()) require.Equal(t, 1, len(bundleEn.Resources)) logoEn := bundleEn.Resources.GetByPrefix("logo") require.NotNil(t, logoEn) require.Equal(t, "/blog/en/bundles/b1/logo.png", logoEn.RelPermalink()) require.Contains(t, readFileFromFs(t, fs.Destination, filepath.FromSlash("public/en/bundles/b1/logo.png")), "PNG Data") } func TestMultiSitesRebuild(t *testing.T) { // t.Parallel() not supported, see https://github.com/fortytw2/leaktest/issues/4 // This leaktest seems to be a little bit shaky on Travis. if !isCI() { defer leaktest.CheckTimeout(t, 30*time.Second)() } siteConfig := testSiteConfig{Running: true, Fs: afero.NewMemMapFs(), DefaultContentLanguage: "fr", DefaultContentLanguageInSubdir: true} sites := createMultiTestSites(t, siteConfig, multiSiteTOMLConfigTemplate) fs := sites.Fs th := testHelper{sites.Cfg, fs, t} cfg := BuildCfg{} err := sites.Build(cfg) if err != nil { t.Fatalf("Failed to build sites: %s", err) } _, err = fs.Destination.Open("public/en/sect/doc2/index.html") if err != nil { t.Fatalf("Unable to locate file") } enSite := sites.Sites[0] frSite := sites.Sites[1] require.Len(t, enSite.RegularPages, 5) require.Len(t, frSite.RegularPages, 4) // Verify translations th.assertFileContent("public/en/sect/doc1-slug/index.html", "Hello") th.assertFileContent("public/fr/sect/doc1/index.html", "Bonjour") // check single page content th.assertFileContent("public/fr/sect/doc1/index.html", "Single", "Shortcode: Bonjour") th.assertFileContent("public/en/sect/doc1-slug/index.html", "Single", "Shortcode: Hello") for i, this := range []struct { preFunc func(t *testing.T) events []fsnotify.Event assertFunc func(t *testing.T) }{ // * Remove doc // * Add docs existing languages // (Add doc new language: TODO(bep) we should load config.toml as part of these so we can add languages). // * Rename file // * Change doc // * Change a template // * Change language file { func(t *testing.T) { fs.Source.Remove("content/sect/doc2.en.md") }, []fsnotify.Event{{Name: filepath.FromSlash("content/sect/doc2.en.md"), Op: fsnotify.Remove}}, func(t *testing.T) { require.Len(t, enSite.RegularPages, 4, "1 en removed") // Check build stats require.Equal(t, 1, enSite.draftCount, "Draft") require.Equal(t, 1, enSite.futureCount, "Future") require.Equal(t, 1, enSite.expiredCount, "Expired") require.Equal(t, 0, frSite.draftCount, "Draft") require.Equal(t, 1, frSite.futureCount, "Future") require.Equal(t, 1, frSite.expiredCount, "Expired") }, }, { func(t *testing.T) { writeNewContentFile(t, fs, "new_en_1", "2016-07-31", "content/new1.en.md", -5) writeNewContentFile(t, fs, "new_en_2", "1989-07-30", "content/new2.en.md", -10) writeNewContentFile(t, fs, "new_fr_1", "2016-07-30", "content/new1.fr.md", 10) }, []fsnotify.Event{ {Name: filepath.FromSlash("content/new1.en.md"), Op: fsnotify.Create}, {Name: filepath.FromSlash("content/new2.en.md"), Op: fsnotify.Create}, {Name: filepath.FromSlash("content/new1.fr.md"), Op: fsnotify.Create}, }, func(t *testing.T) { require.Len(t, enSite.RegularPages, 6) require.Len(t, enSite.AllPages, 34) require.Len(t, frSite.RegularPages, 5) require.Equal(t, "new_fr_1", frSite.RegularPages[3].title) require.Equal(t, "new_en_2", enSite.RegularPages[0].title) require.Equal(t, "new_en_1", enSite.RegularPages[1].title) rendered := readDestination(t, fs, "public/en/new1/index.html") require.True(t, strings.Contains(rendered, "new_en_1"), rendered) }, }, { func(t *testing.T) { p := "content/sect/doc1.en.md" doc1 := readSource(t, fs, p) doc1 += "CHANGED" writeSource(t, fs, p, doc1) }, []fsnotify.Event{{Name: filepath.FromSlash("content/sect/doc1.en.md"), Op: fsnotify.Write}}, func(t *testing.T) { require.Len(t, enSite.RegularPages, 6) doc1 := readDestination(t, fs, "public/en/sect/doc1-slug/index.html") require.True(t, strings.Contains(doc1, "CHANGED"), doc1) }, }, // Rename a file { func(t *testing.T) { if err := fs.Source.Rename("content/new1.en.md", "content/new1renamed.en.md"); err != nil { t.Fatalf("Rename failed: %s", err) } }, []fsnotify.Event{ {Name: filepath.FromSlash("content/new1renamed.en.md"), Op: fsnotify.Rename}, {Name: filepath.FromSlash("content/new1.en.md"), Op: fsnotify.Rename}, }, func(t *testing.T) { require.Len(t, enSite.RegularPages, 6, "Rename") require.Equal(t, "new_en_1", enSite.RegularPages[1].title) rendered := readDestination(t, fs, "public/en/new1renamed/index.html") require.True(t, strings.Contains(rendered, "new_en_1"), rendered) }}, { // Change a template func(t *testing.T) { template := "layouts/_default/single.html" templateContent := readSource(t, fs, template) templateContent += "{{ print \"Template Changed\"}}" writeSource(t, fs, template, templateContent) }, []fsnotify.Event{{Name: filepath.FromSlash("layouts/_default/single.html"), Op: fsnotify.Write}}, func(t *testing.T) { require.Len(t, enSite.RegularPages, 6) require.Len(t, enSite.AllPages, 34) require.Len(t, frSite.RegularPages, 5) doc1 := readDestination(t, fs, "public/en/sect/doc1-slug/index.html") require.True(t, strings.Contains(doc1, "Template Changed"), doc1) }, }, { // Change a language file func(t *testing.T) { languageFile := "i18n/fr.yaml" langContent := readSource(t, fs, languageFile) langContent = strings.Replace(langContent, "Bonjour", "Salut", 1) writeSource(t, fs, languageFile, langContent) }, []fsnotify.Event{{Name: filepath.FromSlash("i18n/fr.yaml"), Op: fsnotify.Write}}, func(t *testing.T) { require.Len(t, enSite.RegularPages, 6) require.Len(t, enSite.AllPages, 34) require.Len(t, frSite.RegularPages, 5) docEn := readDestination(t, fs, "public/en/sect/doc1-slug/index.html") require.True(t, strings.Contains(docEn, "Hello"), "No Hello") docFr := readDestination(t, fs, "public/fr/sect/doc1/index.html") require.True(t, strings.Contains(docFr, "Salut"), "No Salut") homeEn := enSite.getPage(KindHome) require.NotNil(t, homeEn) require.Len(t, homeEn.Translations(), 3) require.Equal(t, "fr", homeEn.Translations()[0].Lang()) }, }, // Change a shortcode { func(t *testing.T) { writeSource(t, fs, "layouts/shortcodes/shortcode.html", "Modified Shortcode: {{ i18n \"hello\" }}") }, []fsnotify.Event{ {Name: filepath.FromSlash("layouts/shortcodes/shortcode.html"), Op: fsnotify.Write}, }, func(t *testing.T) { require.Len(t, enSite.RegularPages, 6) require.Len(t, enSite.AllPages, 34) require.Len(t, frSite.RegularPages, 5) th.assertFileContent("public/fr/sect/doc1/index.html", "Single", "Modified Shortcode: Salut") th.assertFileContent("public/en/sect/doc1-slug/index.html", "Single", "Modified Shortcode: Hello") }, }, } { if this.preFunc != nil { this.preFunc(t) } err = sites.Build(cfg, this.events...) if err != nil { t.Fatalf("[%d] Failed to rebuild sites: %s", i, err) } this.assertFunc(t) } // Check that the drafts etc. are not built/processed/rendered. assertShouldNotBuild(t, sites) } func assertShouldNotBuild(t *testing.T, sites *HugoSites) { s := sites.Sites[0] for _, p := range s.rawAllPages { // No HTML when not processed require.Equal(t, p.shouldBuild(), bytes.Contains(p.workContent, []byte("</")), p.BaseFileName()+": "+string(p.workContent)) require.Equal(t, p.shouldBuild(), p.Content != "", p.BaseFileName()) require.Equal(t, p.shouldBuild(), p.Content != "", p.BaseFileName()) } } func TestAddNewLanguage(t *testing.T) { t.Parallel() siteConfig := testSiteConfig{Fs: afero.NewMemMapFs(), DefaultContentLanguage: "fr", DefaultContentLanguageInSubdir: true} sites := createMultiTestSites(t, siteConfig, multiSiteTOMLConfigTemplate) cfg := BuildCfg{} err := sites.Build(cfg) if err != nil { t.Fatalf("Failed to build sites: %s", err) } fs := sites.Fs newConfig := multiSiteTOMLConfigTemplate + ` [Languages.sv] weight = 15 title = "Svenska" ` newConfig = createConfig(t, siteConfig, newConfig) writeNewContentFile(t, fs, "Swedish Contentfile", "2016-01-01", "content/sect/doc1.sv.md", 10) // replace the config writeSource(t, fs, "multilangconfig.toml", newConfig) // Watching does not work with in-memory fs, so we trigger a reload manually require.NoError(t, sites.Cfg.(*helpers.Language).Cfg.(*viper.Viper).ReadInConfig()) err = sites.Build(BuildCfg{CreateSitesFromConfig: true}) if err != nil { t.Fatalf("Failed to rebuild sites: %s", err) } require.Len(t, sites.Sites, 5, fmt.Sprintf("Len %d", len(sites.Sites))) // The Swedish site should be put in the middle (language weight=15) enSite := sites.Sites[0] svSite := sites.Sites[1] frSite := sites.Sites[2] require.True(t, enSite.Language.Lang == "en", enSite.Language.Lang) require.True(t, svSite.Language.Lang == "sv", svSite.Language.Lang) require.True(t, frSite.Language.Lang == "fr", frSite.Language.Lang) homeEn := enSite.getPage(KindHome) require.NotNil(t, homeEn) require.Len(t, homeEn.Translations(), 4) require.Equal(t, "sv", homeEn.Translations()[0].Lang()) require.Len(t, enSite.RegularPages, 5) require.Len(t, frSite.RegularPages, 4) // Veriy Swedish site require.Len(t, svSite.RegularPages, 1) svPage := svSite.RegularPages[0] require.Equal(t, "Swedish Contentfile", svPage.title) require.Equal(t, "sv", svPage.Lang()) require.Len(t, svPage.Translations(), 2) require.Len(t, svPage.AllTranslations(), 3) require.Equal(t, "en", svPage.Translations()[0].Lang()) // Regular pages have no children require.Len(t, svPage.Pages, 0) require.Len(t, svPage.Data["Pages"], 0) } func TestChangeDefaultLanguage(t *testing.T) { t.Parallel() mf := afero.NewMemMapFs() sites := createMultiTestSites(t, testSiteConfig{Fs: mf, DefaultContentLanguage: "fr", DefaultContentLanguageInSubdir: false}, multiSiteTOMLConfigTemplate) require.Equal(t, mf, sites.Fs.Source) cfg := BuildCfg{} fs := sites.Fs th := testHelper{sites.Cfg, fs, t} err := sites.Build(cfg) if err != nil { t.Fatalf("Failed to build sites: %s", err) } th.assertFileContent("public/sect/doc1/index.html", "Single", "Bonjour") th.assertFileContent("public/en/sect/doc2/index.html", "Single", "Hello") newConfig := createConfig(t, testSiteConfig{Fs: mf, DefaultContentLanguage: "en", DefaultContentLanguageInSubdir: false}, multiSiteTOMLConfigTemplate) // replace the config writeSource(t, fs, "multilangconfig.toml", newConfig) // Watching does not work with in-memory fs, so we trigger a reload manually // This does not look pretty, so we should think of something else. require.NoError(t, th.Cfg.(*helpers.Language).Cfg.(*viper.Viper).ReadInConfig()) err = sites.Build(BuildCfg{CreateSitesFromConfig: true}) if err != nil { t.Fatalf("Failed to rebuild sites: %s", err) } // Default language is now en, so that should now be the "root" language th.assertFileContent("public/fr/sect/doc1/index.html", "Single", "Bonjour") th.assertFileContent("public/sect/doc2/index.html", "Single", "Hello") } func TestTableOfContentsInShortcodes(t *testing.T) { t.Parallel() mf := afero.NewMemMapFs() writeToFs(t, mf, "layouts/shortcodes/toc.html", tocShortcode) writeToFs(t, mf, "content/post/simple.en.md", tocPageSimple) writeToFs(t, mf, "content/post/withSCInHeading.en.md", tocPageWithShortcodesInHeadings) sites := createMultiTestSites(t, testSiteConfig{Fs: mf, DefaultContentLanguage: "en", DefaultContentLanguageInSubdir: true}, multiSiteTOMLConfigTemplate) cfg := BuildCfg{} err := sites.Build(cfg) if err != nil { t.Fatalf("Failed to build sites: %s", err) } fs := sites.Fs th := testHelper{sites.Cfg, fs, t} th.assertFileContent("public/en/post/simple/index.html", tocPageSimpleExpected) th.assertFileContent("public/en/post/withSCInHeading/index.html", tocPageWithShortcodesInHeadingsExpected) } var tocShortcode = ` {{ .Page.TableOfContents }} ` var tocPageSimple = `--- title: tocTest publishdate: "2000-01-01" --- {{< toc >}} # Heading 1 {#1} Some text. ## Subheading 1.1 {#1-1} Some more text. # Heading 2 {#2} Even more text. ## Subheading 2.1 {#2-1} Lorem ipsum... ` var tocPageSimpleExpected = `<nav id="TableOfContents"> <ul> <li><a href="#1">Heading 1</a> <ul> <li><a href="#1-1">Subheading 1.1</a></li> </ul></li> <li><a href="#2">Heading 2</a> <ul> <li><a href="#2-1">Subheading 2.1</a></li> </ul></li> </ul> </nav>` var tocPageWithShortcodesInHeadings = `--- title: tocTest publishdate: "2000-01-01" --- {{< toc >}} # Heading 1 {#1} Some text. ## Subheading 1.1 {{< shortcode >}} {#1-1} Some more text. # Heading 2 {{% shortcode %}} {#2} Even more text. ## Subheading 2.1 {#2-1} Lorem ipsum... ` var tocPageWithShortcodesInHeadingsExpected = `<nav id="TableOfContents"> <ul> <li><a href="#1">Heading 1</a> <ul> <li><a href="#1-1">Subheading 1.1 Shortcode: Hello</a></li> </ul></li> <li><a href="#2">Heading 2 Shortcode: Hello</a> <ul> <li><a href="#2-1">Subheading 2.1</a></li> </ul></li> </ul> </nav>` var multiSiteTOMLConfigTemplate = ` baseURL = "http://example.com/blog" rssURI = "index.xml" paginate = 1 disablePathToLower = true defaultContentLanguage = "{{ .DefaultContentLanguage }}" defaultContentLanguageInSubdir = {{ .DefaultContentLanguageInSubdir }} [permalinks] other = "/somewhere/else/:filename" [blackfriday] angledQuotes = true [Taxonomies] tag = "tags" [Languages] [Languages.en] weight = 10 title = "In English" languageName = "English" [Languages.en.blackfriday] angledQuotes = false [[Languages.en.menu.main]] url = "/" name = "Home" weight = 0 [Languages.fr] weight = 20 title = "Le Français" languageName = "Français" [Languages.fr.Taxonomies] plaque = "plaques" [Languages.nn] weight = 30 title = "På nynorsk" languageName = "Nynorsk" paginatePath = "side" [Languages.nn.Taxonomies] lag = "lag" [[Languages.nn.menu.main]] url = "/" name = "Heim" weight = 1 [Languages.nb] weight = 40 title = "På bokmål" languageName = "Bokmål" paginatePath = "side" [Languages.nb.Taxonomies] lag = "lag" ` var multiSiteYAMLConfigTemplate = ` baseURL: "http://example.com/blog" rssURI: "index.xml" disablePathToLower: true paginate: 1 defaultContentLanguage: "{{ .DefaultContentLanguage }}" defaultContentLanguageInSubdir: {{ .DefaultContentLanguageInSubdir }} permalinks: other: "/somewhere/else/:filename" blackfriday: angledQuotes: true Taxonomies: tag: "tags" Languages: en: weight: 10 title: "In English" languageName: "English" blackfriday: angledQuotes: false menu: main: - url: "/" name: "Home" weight: 0 fr: weight: 20 title: "Le Français" languageName: "Français" Taxonomies: plaque: "plaques" nn: weight: 30 title: "På nynorsk" languageName: "Nynorsk" paginatePath: "side" Taxonomies: lag: "lag" menu: main: - url: "/" name: "Heim" weight: 1 nb: weight: 40 title: "På bokmål" languageName: "Bokmål" paginatePath: "side" Taxonomies: lag: "lag" ` var multiSiteJSONConfigTemplate = ` { "baseURL": "http://example.com/blog", "rssURI": "index.xml", "paginate": 1, "disablePathToLower": true, "defaultContentLanguage": "{{ .DefaultContentLanguage }}", "defaultContentLanguageInSubdir": true, "permalinks": { "other": "/somewhere/else/:filename" }, "blackfriday": { "angledQuotes": true }, "Taxonomies": { "tag": "tags" }, "Languages": { "en": { "weight": 10, "title": "In English", "languageName": "English", "blackfriday": { "angledQuotes": false }, "menu": { "main": [ { "url": "/", "name": "Home", "weight": 0 } ] } }, "fr": { "weight": 20, "title": "Le Français", "languageName": "Français", "Taxonomies": { "plaque": "plaques" } }, "nn": { "weight": 30, "title": "På nynorsk", "paginatePath": "side", "languageName": "Nynorsk", "Taxonomies": { "lag": "lag" }, "menu": { "main": [ { "url": "/", "name": "Heim", "weight": 1 } ] } }, "nb": { "weight": 40, "title": "På bokmål", "paginatePath": "side", "languageName": "Bokmål", "Taxonomies": { "lag": "lag" } } } } ` func createMultiTestSites(t *testing.T, siteConfig testSiteConfig, tomlConfigTemplate string) *HugoSites { return createMultiTestSitesForConfig(t, siteConfig, tomlConfigTemplate, "toml") } func createMultiTestSitesForConfig(t *testing.T, siteConfig testSiteConfig, configTemplate, configSuffix string) *HugoSites { configContent := createConfig(t, siteConfig, configTemplate) mf := siteConfig.Fs // Add some layouts if err := afero.WriteFile(mf, filepath.Join("layouts", "_default/single.html"), []byte("Single: {{ .Title }}|{{ i18n \"hello\" }}|{{.Lang}}|{{ .Content }}"), 0755); err != nil { t.Fatalf("Failed to write layout file: %s", err) } if err := afero.WriteFile(mf, filepath.Join("layouts", "_default/list.html"), []byte("{{ $p := .Paginator }}List Page {{ $p.PageNumber }}: {{ .Title }}|{{ i18n \"hello\" }}|{{ .Permalink }}|Pager: {{ template \"_internal/pagination.html\" . }}"), 0755); err != nil { t.Fatalf("Failed to write layout file: %s", err) } if err := afero.WriteFile(mf, filepath.Join("layouts", "index.html"), []byte("{{ $p := .Paginator }}Default Home Page {{ $p.PageNumber }}: {{ .Title }}|{{ .IsHome }}|{{ i18n \"hello\" }}|{{ .Permalink }}|{{ .Site.Data.hugo.slogan }}"), 0755); err != nil { t.Fatalf("Failed to write layout file: %s", err) } if err := afero.WriteFile(mf, filepath.Join("layouts", "index.fr.html"), []byte("{{ $p := .Paginator }}French Home Page {{ $p.PageNumber }}: {{ .Title }}|{{ .IsHome }}|{{ i18n \"hello\" }}|{{ .Permalink }}|{{ .Site.Data.hugo.slogan }}"), 0755); err != nil { t.Fatalf("Failed to write layout file: %s", err) } // Add a shortcode if err := afero.WriteFile(mf, filepath.Join("layouts", "shortcodes", "shortcode.html"), []byte("Shortcode: {{ i18n \"hello\" }}"), 0755); err != nil { t.Fatalf("Failed to write layout file: %s", err) } // A shortcode in multiple languages if err := afero.WriteFile(mf, filepath.Join("layouts", "shortcodes", "lingo.html"), []byte("LingoDefault"), 0755); err != nil { t.Fatalf("Failed to write layout file: %s", err) } if err := afero.WriteFile(mf, filepath.Join("layouts", "shortcodes", "lingo.fr.html"), []byte("LingoFrench"), 0755); err != nil { t.Fatalf("Failed to write layout file: %s", err) } // Add some language files if err := afero.WriteFile(mf, filepath.Join("i18n", "en.yaml"), []byte(` hello: other: "Hello" `), 0755); err != nil { t.Fatalf("Failed to write language file: %s", err) } if err := afero.WriteFile(mf, filepath.Join("i18n", "fr.yaml"), []byte(` hello: other: "Bonjour" `), 0755); err != nil { t.Fatalf("Failed to write language file: %s", err) } // Sources sources := [][2]string{ {filepath.FromSlash("root.en.md"), `--- title: root weight: 10000 slug: root publishdate: "2000-01-01" --- # root `}, {filepath.FromSlash("sect/doc1.en.md"), `--- title: doc1 weight: 1 slug: doc1-slug tags: - tag1 publishdate: "2000-01-01" --- # doc1 *some "content"* {{< shortcode >}} {{< lingo >}} NOTE: slug should be used as URL `}, {filepath.FromSlash("sect/doc1.fr.md"), `--- title: doc1 weight: 1 plaques: - frtag1 - frtag2 publishdate: "2000-01-04" --- # doc1 *quelque "contenu"* {{< shortcode >}} {{< lingo >}} NOTE: should be in the 'en' Page's 'Translations' field. NOTE: date is after "doc3" `}, {filepath.FromSlash("sect/doc2.en.md"), `--- title: doc2 weight: 2 publishdate: "2000-01-02" --- # doc2 *some content* NOTE: without slug, "doc2" should be used, without ".en" as URL `}, {filepath.FromSlash("sect/doc3.en.md"), `--- title: doc3 weight: 3 publishdate: "2000-01-03" aliases: [/en/al/alias1,/al/alias2/] tags: - tag2 - tag1 url: /superbob --- # doc3 *some content* NOTE: third 'en' doc, should trigger pagination on home page. `}, {filepath.FromSlash("sect/doc4.md"), `--- title: doc4 weight: 4 plaques: - frtag1 publishdate: "2000-01-05" --- # doc4 *du contenu francophone* NOTE: should use the defaultContentLanguage and mark this doc as 'fr'. NOTE: doesn't have any corresponding translation in 'en' `}, {filepath.FromSlash("other/doc5.fr.md"), `--- title: doc5 weight: 5 publishdate: "2000-01-06" --- # doc5 *autre contenu francophone* NOTE: should use the "permalinks" configuration with :filename `}, // Add some for the stats {filepath.FromSlash("stats/expired.fr.md"), `--- title: expired publishdate: "2000-01-06" expiryDate: "2001-01-06" --- # Expired `}, {filepath.FromSlash("stats/future.fr.md"), `--- title: future weight: 6 publishdate: "2100-01-06" --- # Future `}, {filepath.FromSlash("stats/expired.en.md"), `--- title: expired weight: 7 publishdate: "2000-01-06" expiryDate: "2001-01-06" --- # Expired `}, {filepath.FromSlash("stats/future.en.md"), `--- title: future weight: 6 publishdate: "2100-01-06" --- # Future `}, {filepath.FromSlash("stats/draft.en.md"), `--- title: expired publishdate: "2000-01-06" draft: true --- # Draft `}, {filepath.FromSlash("stats/tax.nn.md"), `--- title: Tax NN weight: 8 publishdate: "2000-01-06" weight: 1001 lag: - Sogndal --- # Tax NN `}, {filepath.FromSlash("stats/tax.nb.md"), `--- title: Tax NB weight: 8 publishdate: "2000-01-06" weight: 1002 lag: - Sogndal --- # Tax NB `}, // Bundle {filepath.FromSlash("bundles/b1/index.en.md"), `--- title: Bundle EN publishdate: "2000-01-06" weight: 2001 --- # Bundle Content EN `}, {filepath.FromSlash("bundles/b1/index.md"), `--- title: Bundle Default publishdate: "2000-01-06" weight: 2002 --- # Bundle Content Default `}, {filepath.FromSlash("bundles/b1/logo.png"), ` PNG Data `}, } configFile := "multilangconfig." + configSuffix writeToFs(t, mf, configFile, configContent) cfg, err := LoadConfig(mf, "", configFile) require.NoError(t, err) fs := hugofs.NewFrom(mf, cfg) for _, s := range sources { if err := afero.WriteFile(mf, filepath.Join("content", s[0]), []byte(s[1]), 0755); err != nil { t.Fatalf("Failed to write file: %s", err) } } // Add some data writeSource(t, fs, "data/hugo.toml", "slogan = \"Hugo Rocks!\"") sites, err := NewHugoSites(deps.DepsCfg{Fs: fs, Cfg: cfg, Running: siteConfig.Running}) //, Logger: newDebugLogger()}) if err != nil { t.Fatalf("Failed to create sites: %s", err) } if len(sites.Sites) == 0 { t.Fatalf("Got %d sites", len(sites.Sites)) } if sites.Fs.Source != mf { t.Fatal("FS mismatch") } return sites } func writeSource(t testing.TB, fs *hugofs.Fs, filename, content string) { writeToFs(t, fs.Source, filename, content) } func writeToFs(t testing.TB, fs afero.Fs, filename, content string) { if err := afero.WriteFile(fs, filepath.FromSlash(filename), []byte(content), 0755); err != nil { t.Fatalf("Failed to write file: %s", err) } } func readDestination(t testing.TB, fs *hugofs.Fs, filename string) string { return readFileFromFs(t, fs.Destination, filename) } func destinationExists(fs *hugofs.Fs, filename string) bool { b, err := helpers.Exists(filename, fs.Destination) if err != nil { panic(err) } return b } func readSource(t *testing.T, fs *hugofs.Fs, filename string) string { return readFileFromFs(t, fs.Source, filename) } func readFileFromFs(t testing.TB, fs afero.Fs, filename string) string { filename = filepath.FromSlash(filename) b, err := afero.ReadFile(fs, filename) if err != nil { // Print some debug info root := "" //strings.Split(filename, helpers.FilePathSeparator)[0] afero.Walk(fs, root, func(path string, info os.FileInfo, err error) error { if info != nil && !info.IsDir() { fmt.Println(" ", path) } return nil }) t.Fatalf("Failed to read file: %s", err) } return string(b) } const testPageTemplate = `--- title: "%s" publishdate: "%s" weight: %d --- # Doc %s ` func newTestPage(title, date string, weight int) string { return fmt.Sprintf(testPageTemplate, title, date, weight, title) } func writeNewContentFile(t *testing.T, fs *hugofs.Fs, title, date, filename string, weight int) { content := newTestPage(title, date, weight) writeSource(t, fs, filename, content) } func createConfig(t *testing.T, config testSiteConfig, configTemplate string) string { templ, err := template.New("test").Parse(configTemplate) if err != nil { t.Fatal("Template parse failed:", err) } var b bytes.Buffer templ.Execute(&b, config) return b.String() }