package hugolib import ( "fmt" "strings" "testing" "path/filepath" "time" "github.com/gohugoio/hugo/resources/page" "github.com/fortytw2/leaktest" "github.com/fsnotify/fsnotify" "github.com/gohugoio/hugo/helpers" "github.com/gohugoio/hugo/hugofs" "github.com/spf13/afero" "github.com/stretchr/testify/require" ) func TestMultiSitesMainLangInRoot(t *testing.T) { t.Parallel() for _, b := range []bool{false} { doTestMultiSitesMainLangInRoot(t, b) } } func doTestMultiSitesMainLangInRoot(t *testing.T, defaultInSubDir bool) { assert := require.New(t) siteConfig := map[string]interface{}{ "DefaultContentLanguage": "fr", "DefaultContentLanguageInSubdir": defaultInSubDir, } b := newMultiSiteTestBuilder(t, "toml", multiSiteTOMLConfigTemplate, siteConfig) pathMod := func(s string) string { return s } if !defaultInSubDir { pathMod = func(s string) string { return strings.Replace(s, "/fr/", "/", -1) } } b.CreateSites() b.Build(BuildCfg{}) sites := b.H.Sites require.Len(t, sites, 4) enSite := sites[0] frSite := sites[1] assert.Equal("/en", enSite.Info.LanguagePrefix) if defaultInSubDir { assert.Equal("/fr", frSite.Info.LanguagePrefix) } else { assert.Equal("", frSite.Info.LanguagePrefix) } assert.Equal("/blog/en/foo", enSite.PathSpec.RelURL("foo", true)) doc1en := enSite.RegularPages()[0] doc1fr := frSite.RegularPages()[0] enPerm := doc1en.Permalink() enRelPerm := doc1en.RelPermalink() assert.Equal("http://example.com/blog/en/sect/doc1-slug/", enPerm) assert.Equal("/blog/en/sect/doc1-slug/", enRelPerm) frPerm := doc1fr.Permalink() frRelPerm := doc1fr.RelPermalink() b.AssertFileContent(pathMod("public/fr/sect/doc1/index.html"), "Single", "Bonjour") b.AssertFileContent("public/en/sect/doc1-slug/index.html", "Single", "Hello") if defaultInSubDir { assert.Equal("http://example.com/blog/fr/sect/doc1/", frPerm) assert.Equal("/blog/fr/sect/doc1/", frRelPerm) // should have a redirect on top level. b.AssertFileContent("public/index.html", ``) } else { // Main language in root assert.Equal("http://example.com/blog/sect/doc1/", frPerm) assert.Equal("/blog/sect/doc1/", frRelPerm) // should have redirect back to root b.AssertFileContent("public/fr/index.html", ``) } b.AssertFileContent(pathMod("public/fr/index.html"), "Home", "Bonjour") b.AssertFileContent("public/en/index.html", "Home", "Hello") // Check list pages b.AssertFileContent(pathMod("public/fr/sect/index.html"), "List", "Bonjour") b.AssertFileContent("public/en/sect/index.html", "List", "Hello") b.AssertFileContent(pathMod("public/fr/plaques/FRtag1/index.html"), "Taxonomy List", "Bonjour") b.AssertFileContent("public/en/tags/tag1/index.html", "Taxonomy 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. b.AssertFileContent("public/sitemap.xml", "http://example.com/blog/en/sitemap.xml", "http://example.com/blog/fr/sitemap.xml") if defaultInSubDir { b.AssertFileContent("public/fr/sitemap.xml", "http://example.com/blog/fr/") } else { b.AssertFileContent("public/fr/sitemap.xml", "http://example.com/blog/") } b.AssertFileContent("public/en/sitemap.xml", "http://example.com/blog/en/") // Check rss b.AssertFileContent(pathMod("public/fr/index.xml"), pathMod(`http://example.com/blog/en/sitemap.xml", "http://example.com/blog/fr/sitemap.xml") b.AssertFileContent("public/en/sitemap.xml", "http://example.com/blog/en/sect/doc2/") b.AssertFileContent("public/fr/sitemap.xml", "http://example.com/blog/fr/sect/doc1/") // 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"]) b.AssertFileContent("public/fr/plaques/FRtag1/index.html", "FRtag1|Bonjour|http://example.com/blog/fr/plaques/FRtag1/") b.AssertFileContent("public/en/tags/tag1/index.html", "tag1|Hello|http://example.com/blog/en/tags/tag1/") // Check Blackfriday config require.True(t, strings.Contains(content(doc1fr), "«"), content(doc1fr)) require.False(t, strings.Contains(content(doc1en), "«"), content(doc1en)) require.True(t, strings.Contains(content(doc1en), "“"), content(doc1en)) // 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 #3108 prevPage := enSite.RegularPages()[0].Prev() require.NotNil(t, prevPage) require.Equal(t, page.KindPage, prevPage.Kind()) for { if prevPage == nil { break } require.Equal(t, page.KindPage, prevPage.Kind()) prevPage = prevPage.Prev() } // Check bundles b.AssertFileContent("public/fr/bundles/b1/index.html", "RelPermalink: /blog/fr/bundles/b1/|") bundleFr := frSite.getPage(page.KindPage, "bundles/b1/index.md") require.NotNil(t, bundleFr) require.Equal(t, 1, len(bundleFr.Resources())) logoFr := bundleFr.Resources().GetMatch("logo*") require.NotNil(t, logoFr) b.AssertFileContent("public/fr/bundles/b1/index.html", "Resources: image/png: /blog/fr/bundles/b1/logo.png") b.AssertFileContent("public/fr/bundles/b1/logo.png", "PNG Data") bundleEn := enSite.getPage(page.KindPage, "bundles/b1/index.en.md") require.NotNil(t, bundleEn) b.AssertFileContent("public/en/bundles/b1/index.html", "RelPermalink: /blog/en/bundles/b1/|") require.Equal(t, 1, len(bundleEn.Resources())) logoEn := bundleEn.Resources().GetMatch("logo*") require.NotNil(t, logoEn) b.AssertFileContent("public/en/bundles/b1/index.html", "Resources: image/png: /blog/en/bundles/b1/logo.png") b.AssertFileContent("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, 10*time.Second)() } assert := require.New(t) b := newMultiSiteTestDefaultBuilder(t).Running().CreateSites().Build(BuildCfg{}) sites := b.H.Sites fs := b.Fs b.AssertFileContent("public/en/sect/doc2/index.html", "Single: doc2|Hello|en|", "\n\n

doc2

\n\n

some content") enSite := sites[0] frSite := sites[1] assert.Len(enSite.RegularPages(), 5) assert.Len(frSite.RegularPages(), 4) // Verify translations b.AssertFileContent("public/en/sect/doc1-slug/index.html", "Hello") b.AssertFileContent("public/fr/sect/doc1/index.html", "Bonjour") // check single page content b.AssertFileContent("public/fr/sect/doc1/index.html", "Single", "Shortcode: Bonjour") b.AssertFileContent("public/en/sect/doc1-slug/index.html", "Single", "Shortcode: Hello") homeEn := enSite.getPage(page.KindHome) require.NotNil(t, homeEn) assert.Len(homeEn.Translations(), 3) contentFs := b.H.Fs.Source 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) { assert.Len(enSite.RegularPages(), 4, "1 en removed") // Check build stats require.Equal(t, 1, enSite.buildStats.draftCount, "Draft") require.Equal(t, 1, enSite.buildStats.futureCount, "Future") require.Equal(t, 1, enSite.buildStats.expiredCount, "Expired") require.Equal(t, 0, frSite.buildStats.draftCount, "Draft") require.Equal(t, 1, frSite.buildStats.futureCount, "Future") require.Equal(t, 1, frSite.buildStats.expiredCount, "Expired") }, }, { func(t *testing.T) { writeNewContentFile(t, contentFs, "new_en_1", "2016-07-31", "content/new1.en.md", -5) writeNewContentFile(t, contentFs, "new_en_2", "1989-07-30", "content/new2.en.md", -10) writeNewContentFile(t, contentFs, "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) { assert.Len(enSite.RegularPages(), 6) assert.Len(enSite.AllPages(), 34) assert.Len(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 := readFileFromFs(t, contentFs, p) doc1 += "CHANGED" writeToFs(t, contentFs, p, doc1) }, []fsnotify.Event{{Name: filepath.FromSlash("content/sect/doc1.en.md"), Op: fsnotify.Write}}, func(t *testing.T) { assert.Len(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 := contentFs.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) { assert.Len(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) { assert.Len(enSite.RegularPages(), 6) assert.Len(enSite.AllPages(), 34) assert.Len(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) { assert.Len(enSite.RegularPages(), 6) assert.Len(enSite.AllPages(), 34) assert.Len(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(page.KindHome) require.NotNil(t, homeEn) assert.Len(homeEn.Translations(), 3) require.Equal(t, "fr", homeEn.Translations()[0].Language().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) { assert.Len(enSite.RegularPages(), 6) assert.Len(enSite.AllPages(), 34) assert.Len(frSite.RegularPages(), 5) b.AssertFileContent("public/fr/sect/doc1/index.html", "Single", "Modified Shortcode: Salut") b.AssertFileContent("public/en/sect/doc1-slug/index.html", "Single", "Modified Shortcode: Hello") }, }, } { if this.preFunc != nil { this.preFunc(t) } err := b.H.Build(BuildCfg{}, this.events...) if err != nil { t.Fatalf("[%d] Failed to rebuild sites: %s", i, err) } this.assertFunc(t) } } func TestAddNewLanguage(t *testing.T) { t.Parallel() assert := require.New(t) b := newMultiSiteTestDefaultBuilder(t) b.CreateSites().Build(BuildCfg{}) fs := b.Fs newConfig := multiSiteTOMLConfigTemplate + ` [Languages.sv] weight = 15 title = "Svenska" ` writeNewContentFile(t, fs.Source, "Swedish Contentfile", "2016-01-01", "content/sect/doc1.sv.md", 10) // replace the config b.WithNewConfig(newConfig) sites := b.H assert.NoError(b.LoadConfig()) err := b.H.Build(BuildCfg{NewConfig: b.Cfg}) 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(page.KindHome) require.NotNil(t, homeEn) require.Len(t, homeEn.Translations(), 4) require.Equal(t, "sv", homeEn.Translations()[0].Language().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.Language().Lang) require.Len(t, svPage.Translations(), 2) require.Len(t, svPage.AllTranslations(), 3) require.Equal(t, "en", svPage.Translations()[0].Language().Lang) // Regular pages have no children require.Len(t, svPage.Pages(), 0) require.Len(t, svPage.Data().(page.Data).Pages(), 0) } // https://github.com/gohugoio/hugo/issues/4706 func TestContentStressTest(t *testing.T) { b := newTestSitesBuilder(t) numPages := 500 contentTempl := ` --- %s title: %q weight: %d multioutput: %t --- # Header CONTENT The End. ` contentTempl = strings.Replace(contentTempl, "CONTENT", strings.Repeat(` ## Another header Some text. Some more text. `, 100), -1) var content []string defaultOutputs := `outputs: ["html", "json", "rss" ]` for i := 1; i <= numPages; i++ { outputs := defaultOutputs multioutput := true if i%3 == 0 { outputs = `outputs: ["json"]` multioutput = false } section := "s1" if i%10 == 0 { section = "s2" } content = append(content, []string{fmt.Sprintf("%s/page%d.md", section, i), fmt.Sprintf(contentTempl, outputs, fmt.Sprintf("Title %d", i), i, multioutput)}...) } content = append(content, []string{"_index.md", fmt.Sprintf(contentTempl, defaultOutputs, fmt.Sprintf("Home %d", 0), 0, true)}...) content = append(content, []string{"s1/_index.md", fmt.Sprintf(contentTempl, defaultOutputs, fmt.Sprintf("S %d", 1), 1, true)}...) content = append(content, []string{"s2/_index.md", fmt.Sprintf(contentTempl, defaultOutputs, fmt.Sprintf("S %d", 2), 2, true)}...) b.WithSimpleConfigFile() b.WithTemplates("layouts/_default/single.html", `Single: {{ .Content }}|RelPermalink: {{ .RelPermalink }}|Permalink: {{ .Permalink }}`) b.WithTemplates("layouts/_default/myview.html", `View: {{ len .Content }}`) b.WithTemplates("layouts/_default/single.json", `Single JSON: {{ .Content }}|RelPermalink: {{ .RelPermalink }}|Permalink: {{ .Permalink }}`) b.WithTemplates("layouts/_default/list.html", ` Page: {{ .Paginator.PageNumber }} P: {{ with .File }}{{ path.Join .Path }}{{ end }} List: {{ len .Paginator.Pages }}|List Content: {{ len .Content }} {{ $shuffled := where .Site.RegularPages "Params.multioutput" true | shuffle }} {{ $first5 := $shuffled | first 5 }} L1: {{ len .Site.RegularPages }} L2: {{ len $first5 }} {{ range $i, $e := $first5 }} Render {{ $i }}: {{ .Render "myview" }} {{ end }} END `) b.WithContent(content...) b.CreateSites().Build(BuildCfg{}) contentMatchers := []string{"

Another header

", "

Another header

", "

The End.

"} for i := 1; i <= numPages; i++ { if i%3 != 0 { section := "s1" if i%10 == 0 { section = "s2" } checkContent(b, fmt.Sprintf("public/%s/page%d/index.html", section, i), contentMatchers...) } } for i := 1; i <= numPages; i++ { section := "s1" if i%10 == 0 { section = "s2" } checkContent(b, fmt.Sprintf("public/%s/page%d/index.json", section, i), contentMatchers...) } checkContent(b, "public/s1/index.html", "P: s1/_index.md\nList: 10|List Content: 8335\n\n\nL1: 500 L2: 5\n\nRender 0: View: 8335\n\nRender 1: View: 8335\n\nRender 2: View: 8335\n\nRender 3: View: 8335\n\nRender 4: View: 8335\n\nEND\n") checkContent(b, "public/s2/index.html", "P: s2/_index.md\nList: 10|List Content: 8335", "Render 4: View: 8335\n\nEND") checkContent(b, "public/index.html", "P: _index.md\nList: 10|List Content: 8335", "4: View: 8335\n\nEND") // Check paginated pages for i := 2; i <= 9; i++ { checkContent(b, fmt.Sprintf("public/page/%d/index.html", i), fmt.Sprintf("Page: %d", i), "Content: 8335\n\n\nL1: 500 L2: 5\n\nRender 0: View: 8335", "Render 4: View: 8335\n\nEND") } } func checkContent(s *sitesBuilder, filename string, matches ...string) { content := readDestination(s.T, s.Fs, filename) for _, match := range matches { if !strings.Contains(content, match) { s.Fatalf("No match for %q in content for %s\n%q", match, filename, content) } } } func TestTranslationsFromContentToNonContent(t *testing.T) { b := newTestSitesBuilder(t) b.WithConfigFile("toml", ` baseURL = "http://example.com/" defaultContentLanguage = "en" [languages] [languages.en] weight = 10 contentDir = "content/en" [languages.nn] weight = 20 contentDir = "content/nn" `) b.WithContent("en/mysection/_index.md", ` --- Title: My Section --- `) b.WithContent("en/_index.md", ` --- Title: My Home --- `) b.WithContent("en/categories/mycat/_index.md", ` --- Title: My MyCat --- `) b.WithContent("en/categories/_index.md", ` --- Title: My categories --- `) for _, lang := range []string{"en", "nn"} { b.WithContent(lang+"/mysection/page.md", ` --- Title: My Page categories: ["mycat"] --- `) } b.Build(BuildCfg{}) for _, path := range []string{ "/", "/mysection", "/categories", "/categories/mycat", } { t.Run(path, func(t *testing.T) { assert := require.New(t) s1, _ := b.H.Sites[0].getPageNew(nil, path) s2, _ := b.H.Sites[1].getPageNew(nil, path) assert.NotNil(s1) assert.NotNil(s2) assert.Equal(1, len(s1.Translations())) assert.Equal(1, len(s2.Translations())) assert.Equal(s2, s1.Translations()[0]) assert.Equal(s1, s2.Translations()[0]) m1 := s1.Translations().MergeByLanguage(s2.Translations()) m2 := s2.Translations().MergeByLanguage(s1.Translations()) assert.Equal(1, len(m1)) assert.Equal(1, len(m2)) }) } } // https://github.com/gohugoio/hugo/issues/5777 func TestTableOfContentsInShortcodes(t *testing.T) { t.Parallel() b := newMultiSiteTestDefaultBuilder(t) b.WithTemplatesAdded("layouts/shortcodes/toc.html", tocShortcode) b.WithTemplatesAdded("layouts/shortcodes/wrapper.html", "{{ .Inner }}") b.WithContent("post/simple.en.md", tocPageSimple) b.WithContent("post/variants1.en.md", tocPageVariants1) b.WithContent("post/variants2.en.md", tocPageVariants2) b.WithContent("post/withSCInHeading.en.md", tocPageWithShortcodesInHeadings) b.CreateSites().Build(BuildCfg{}) b.AssertFileContent("public/en/post/simple/index.html", tocPageSimpleExpected, // Make sure it is inserted twice `TOC1: