ref: 487b210fb8a31b3636030ea960f6565b9e6b3c54
parent: c80308e6b3f3f4f6879ee6b7301575f162ac9209
author: Bjørn Erik Pedersen <bjorn.erik.pedersen@gmail.com>
date: Fri Nov 11 06:35:55 EST 2016
node to page: Handle Date and Lastmod Updates #2297
--- a/hugolib/hugo_sites.go
+++ b/hugolib/hugo_sites.go
@@ -31,11 +31,6 @@
jww "github.com/spf13/jwalterweatherman"
)
-// Temporary feature flag to ease the refactoring of node vs page, see
-// https://github.com/spf13/hugo/issues/2297
-// TODO(bep) eventually remove
-var nodePageFeatureFlag bool = true
-
// HugoSites represents the sites to build. Each site represents a language.
type HugoSites struct {Sites []*Site
@@ -313,8 +308,6 @@
return &Page{PageType: typ,
Node: Node{- Date: s.Info.LastChange,
- Lastmod: s.Info.LastChange,
Data: make(map[string]interface{}),Site: &s.Info,
language: s.Language,
--- /dev/null
+++ b/hugolib/hugo_sites_build_test.go
@@ -1,0 +1,1265 @@
+package hugolib
+
+import (
+ "bytes"
+ "fmt"
+ "regexp"
+ "strings"
+ "testing"
+
+ "os"
+ "path/filepath"
+ "text/template"
+
+ "github.com/fortytw2/leaktest"
+ "github.com/fsnotify/fsnotify"
+ "github.com/spf13/afero"
+ "github.com/spf13/hugo/helpers"
+ "github.com/spf13/hugo/hugofs"
+ "github.com/spf13/hugo/source"
+ // jww "github.com/spf13/jwalterweatherman"
+ "github.com/spf13/viper"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+type testSiteConfig struct {+ DefaultContentLanguage string
+}
+
+func init() {+ testCommonResetState()
+}
+
+func testCommonResetState() {+ hugofs.InitMemFs()
+ viper.Reset()
+ viper.SetFs(hugofs.Source())
+ helpers.ResetConfigProvider()
+ loadDefaultSettings()
+
+ // Default is false, but true is easier to use as default in tests
+ viper.Set("defaultContentLanguageInSubdir", true)+
+ if err := hugofs.Source().Mkdir("content", 0755); err != nil {+ panic("Content folder creation failed.")+ }
+
+}
+
+func TestMultiSitesMainLangInRoot(t *testing.T) {+ //jww.SetStdoutThreshold(jww.LevelDebug)
+
+ for _, b := range []bool{true, false} {+ doTestMultiSitesMainLangInRoot(t, b)
+ }
+}
+
+func doTestMultiSitesMainLangInRoot(t *testing.T, defaultInSubDir bool) {+ testCommonResetState()
+ viper.Set("defaultContentLanguageInSubdir", defaultInSubDir)+ siteConfig := testSiteConfig{DefaultContentLanguage: "fr"}+
+ sites := createMultiTestSites(t, siteConfig, multiSiteTOMLConfigTemplate)
+
+ 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.Info.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, replaceDefaultContentLanguageValue("http://example.com/blog/fr/sect/doc1/", defaultInSubDir), frPerm)+ require.Equal(t, replaceDefaultContentLanguageValue("/blog/fr/sect/doc1/", defaultInSubDir), frRelPerm)+
+ assertFileContent(t, "public/fr/sect/doc1/index.html", defaultInSubDir, "Single", "Bonjour")
+ assertFileContent(t, "public/en/sect/doc1-slug/index.html", defaultInSubDir, "Single", "Hello")
+
+ // Check home
+ if defaultInSubDir {+ // should have a redirect on top level.
+ assertFileContent(t, "public/index.html", true, `<meta http-equiv="refresh" content="0; url=http://example.com/blog/fr" />`)
+ } else {+ // should have redirect back to root
+ assertFileContent(t, "public/fr/index.html", true, `<meta http-equiv="refresh" content="0; url=http://example.com/blog" />`)
+ }
+ assertFileContent(t, "public/fr/index.html", defaultInSubDir, "Home", "Bonjour")
+ assertFileContent(t, "public/en/index.html", defaultInSubDir, "Home", "Hello")
+
+ // Check list pages
+ assertFileContent(t, "public/fr/sect/index.html", defaultInSubDir, "List", "Bonjour")
+ assertFileContent(t, "public/en/sect/index.html", defaultInSubDir, "List", "Hello")
+ assertFileContent(t, "public/fr/plaques/frtag1/index.html", defaultInSubDir, "List", "Bonjour")
+ assertFileContent(t, "public/en/tags/tag1/index.html", defaultInSubDir, "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.
+ assertFileContent(t, "public/sitemap.xml", true,
+ "<loc>http://example.com/blog/en/sitemap.xml</loc>",
+ "<loc>http://example.com/blog/fr/sitemap.xml</loc>")
+
+ if defaultInSubDir {+ assertFileContent(t, "public/fr/sitemap.xml", true, "<loc>http://example.com/blog/fr/</loc>")
+ } else {+ assertFileContent(t, "public/fr/sitemap.xml", true, "<loc>http://example.com/blog/</loc>")
+ }
+ assertFileContent(t, "public/en/sitemap.xml", true, "<loc>http://example.com/blog/en/</loc>")
+
+ // Check rss
+ assertFileContent(t, "public/fr/index.xml", defaultInSubDir, `<atom:link href="http://example.com/blog/fr/index.xml"`)
+ assertFileContent(t, "public/en/index.xml", defaultInSubDir, `<atom:link href="http://example.com/blog/en/index.xml"`)
+ assertFileContent(t, "public/fr/sect/index.xml", defaultInSubDir, `<atom:link href="http://example.com/blog/fr/sect/index.xml"`)
+ assertFileContent(t, "public/en/sect/index.xml", defaultInSubDir, `<atom:link href="http://example.com/blog/en/sect/index.xml"`)
+ assertFileContent(t, "public/fr/plaques/frtag1/index.xml", defaultInSubDir, `<atom:link href="http://example.com/blog/fr/plaques/frtag1/index.xml"`)
+ assertFileContent(t, "public/en/tags/tag1/index.xml", defaultInSubDir, `<atom:link href="http://example.com/blog/en/tags/tag1/index.xml"`)
+
+ // Check paginators
+ assertFileContent(t, "public/fr/page/1/index.html", defaultInSubDir, `refresh" content="0; url=http://example.com/blog/fr/"`)
+ assertFileContent(t, "public/en/page/1/index.html", defaultInSubDir, `refresh" content="0; url=http://example.com/blog/en/"`)
+ assertFileContent(t, "public/fr/page/2/index.html", defaultInSubDir, "Home Page 2", "Bonjour", "http://example.com/blog/fr/")
+ assertFileContent(t, "public/en/page/2/index.html", defaultInSubDir, "Home Page 2", "Hello", "http://example.com/blog/en/")
+ assertFileContent(t, "public/fr/sect/page/1/index.html", defaultInSubDir, `refresh" content="0; url=http://example.com/blog/fr/sect/"`)
+ assertFileContent(t, "public/en/sect/page/1/index.html", defaultInSubDir, `refresh" content="0; url=http://example.com/blog/en/sect/"`)
+ assertFileContent(t, "public/fr/sect/page/2/index.html", defaultInSubDir, "List Page 2", "Bonjour", "http://example.com/blog/fr/sect/")
+ assertFileContent(t, "public/en/sect/page/2/index.html", defaultInSubDir, "List Page 2", "Hello", "http://example.com/blog/en/sect/")
+ assertFileContent(t, "public/fr/plaques/frtag1/page/1/index.html", defaultInSubDir, `refresh" content="0; url=http://example.com/blog/fr/plaques/frtag1/"`)
+ assertFileContent(t, "public/en/tags/tag1/page/1/index.html", defaultInSubDir, `refresh" content="0; url=http://example.com/blog/en/tags/tag1/"`)
+ assertFileContent(t, "public/fr/plaques/frtag1/page/2/index.html", defaultInSubDir, "List Page 2", "Bonjour", "http://example.com/blog/fr/plaques/frtag1/")
+ assertFileContent(t, "public/en/tags/tag1/page/2/index.html", defaultInSubDir, "List Page 2", "Hello", "http://example.com/blog/en/tags/tag1/")
+ // nn (Nynorsk) and nb (Bokmål) have custom pagePath: side ("page" in Norwegian)+ assertFileContent(t, "public/nn/side/1/index.html", defaultInSubDir, `refresh" content="0; url=http://example.com/blog/nn/"`)
+ assertFileContent(t, "public/nb/side/1/index.html", defaultInSubDir, `refresh" content="0; url=http://example.com/blog/nb/"`)
+}
+
+func replaceDefaultContentLanguageValue(value string, defaultInSubDir bool) string {+ replace := viper.GetString("defaultContentLanguage") + "/"+ if !defaultInSubDir {+ value = strings.Replace(value, replace, "", 1)
+
+ }
+ return value
+
+}
+
+func assertFileContent(t *testing.T, filename string, defaultInSubDir bool, matches ...string) {+ filename = replaceDefaultContentLanguageValue(filename, defaultInSubDir)
+ content := readDestination(t, filename)
+ for _, match := range matches {+ match = replaceDefaultContentLanguageValue(match, defaultInSubDir)
+ require.True(t, strings.Contains(content, match), fmt.Sprintf("File no match for\n%q in\n%q:\n%s", match, filename, content))+ }
+}
+
+func assertFileContentRegexp(t *testing.T, filename string, defaultInSubDir bool, matches ...string) {+ filename = replaceDefaultContentLanguageValue(filename, defaultInSubDir)
+ content := readDestination(t, filename)
+ for _, match := range matches {+ match = replaceDefaultContentLanguageValue(match, defaultInSubDir)
+ r := regexp.MustCompile(match)
+ require.True(t, r.MatchString(content), fmt.Sprintf("File no match for\n%q in\n%q:\n%s", match, filename, content))+ }
+}
+
+//
+func TestMultiSitesBuild(t *testing.T) {+ for _, config := range []struct {+ content string
+ suffix string
+ }{+ {multiSiteTOMLConfigTemplate, "toml"},+ {multiSiteYAMLConfig, "yml"},+ {multiSiteJSONConfig, "json"},+ } {+ doTestMultiSitesBuild(t, config.content, config.suffix)
+ }
+}
+
+func doTestMultiSitesBuild(t *testing.T, configTemplate, configSuffix string) {+ defer leaktest.Check(t)()
+ testCommonResetState()
+ siteConfig := testSiteConfig{DefaultContentLanguage: "fr"}+ sites := createMultiTestSitesForConfig(t, siteConfig, configTemplate, configSuffix)
+
+ err := sites.Build(BuildCfg{})+
+ if err != nil {+ t.Fatalf("Failed to build sites: %s", err)+ }
+
+ enSite := sites.Sites[0]
+
+ assert.Equal(t, "en", enSite.Language.Lang)
+
+ if len(enSite.regularPages) != 4 {+ t.Fatal("Expected 4 english pages")+ }
+ assert.Len(t, enSite.Source.Files(), 14, "should have 13 source files")
+ assert.Len(t, enSite.AllPages, 28, "should have 28 total pages (including translations and index types)")
+
+ doc1en := enSite.regularPages[0]
+ permalink, err := doc1en.Permalink()
+ assert.NoError(t, err, "permalink call failed")
+ assert.Equal(t, "http://example.com/blog/en/sect/doc1-slug/", permalink, "invalid doc1.en permalink")
+ assert.Len(t, doc1en.Translations(), 1, "doc1-en should have one translation, excluding itself")
+
+ doc2 := enSite.regularPages[1]
+ permalink, err = doc2.Permalink()
+ assert.NoError(t, err, "permalink call failed")
+ assert.Equal(t, "http://example.com/blog/en/sect/doc2/", permalink, "invalid doc2 permalink")
+
+ doc3 := enSite.regularPages[2]
+ permalink, err = doc3.Permalink()
+ assert.NoError(t, err, "permalink call failed")
+ // 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.
+ assert.Equal(t, "http://example.com/blog/superbob", permalink, "invalid doc3 permalink")
+
+ assert.Equal(t, "/superbob", doc3.URL(), "invalid url, was specified on doc3")
+ assertFileContent(t, "public/superbob/index.html", true, "doc3|Hello|en")
+ assert.Equal(t, doc2.Next, doc3, "doc3 should follow doc2, in .Next")
+
+ doc1fr := doc1en.Translations()[0]
+ permalink, err = doc1fr.Permalink()
+ assert.NoError(t, err, "permalink call failed")
+ assert.Equal(t, "http://example.com/blog/fr/sect/doc1/", permalink, "invalid doc1fr permalink")
+
+ assert.Equal(t, doc1en.Translations()[0], doc1fr, "doc1-en should have doc1-fr as translation")
+ assert.Equal(t, doc1fr.Translations()[0], doc1en, "doc1-fr should have doc1-en as translation")
+ assert.Equal(t, "fr", doc1fr.Language().Lang)
+
+ doc4 := enSite.AllPages[4]
+ permalink, err = doc4.Permalink()
+ assert.NoError(t, err, "permalink call failed")
+ assert.Equal(t, "http://example.com/blog/fr/sect/doc4/", permalink, "invalid doc4 permalink")
+ assert.Equal(t, "/blog/fr/sect/doc4/", doc4.URL())
+
+ assert.Len(t, doc4.Translations(), 0, "found translations for doc4")
+
+ doc5 := enSite.AllPages[5]
+ permalink, err = doc5.Permalink()
+ assert.NoError(t, err, "permalink call failed")
+ assert.Equal(t, "http://example.com/blog/fr/somewhere/else/doc5", permalink, "invalid doc5 permalink")
+
+ // Taxonomies and their URLs
+ assert.Len(t, enSite.Taxonomies, 1, "should have 1 taxonomy")
+ tags := enSite.Taxonomies["tags"]
+ assert.Len(t, tags, 2, "should have 2 different tags")
+ assert.Equal(t, tags["tag1"][0].Page, doc1en, "first tag1 page should be doc1")
+
+ frSite := sites.Sites[1]
+
+ assert.Equal(t, "fr", frSite.Language.Lang)
+ assert.Len(t, frSite.regularPages, 3, "should have 3 pages")
+ assert.Len(t, frSite.AllPages, 28, "should have 28 total pages (including translations and nodes)")
+
+ for _, frenchPage := range frSite.regularPages {+ assert.Equal(t, "fr", frenchPage.Lang())
+ }
+
+ // Check redirect to main language, French
+ languageRedirect := readDestination(t, "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)
+ assertFileContent(t, "public/en/index.html", true, "Home Page 1", "Hello", "Hugo Rocks!")
+ assertFileContent(t, "public/fr/index.html", true, "Home Page 1", "Bonjour", "Hugo Rocks!")
+
+ // check single page content
+ assertFileContent(t, "public/fr/sect/doc1/index.html", true, "Single", "Shortcode: Bonjour")
+ assertFileContent(t, "public/en/sect/doc1-slug/index.html", true, "Single", "Shortcode: Hello")
+
+ // Check node translations
+ homeEn := enSite.getPage(PageHome)
+ 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(PageSection, "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(PageTaxonomyTerm, "lag")
+ require.NotNil(t, taxNn)
+ require.Len(t, taxNn.Translations(), 1)
+ require.Equal(t, "nb", taxNn.Translations()[0].Lang())
+
+ taxTermNn := nnSite.getPage(PageTaxonomy, "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, "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, "public/en/sitemap.xml")
+ sitemapFr := readDestination(t, "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, "public/fr/plaques/frtag1/index.html")
+ readDestination(t, "public/en/tags/tag1/index.html")
+
+ // Check Blackfriday config
+ assert.True(t, strings.Contains(string(doc1fr.Content), "«"), string(doc1fr.Content))
+ assert.False(t, strings.Contains(string(doc1en.Content), "«"), string(doc1en.Content))
+ assert.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)
+
+}
+
+func TestMultiSitesRebuild(t *testing.T) {+
+ defer leaktest.Check(t)()
+ testCommonResetState()
+ siteConfig := testSiteConfig{DefaultContentLanguage: "fr"}+ sites := createMultiTestSites(t, siteConfig, multiSiteTOMLConfigTemplate)
+ cfg := BuildCfg{Watching: true}+
+ err := sites.Build(cfg)
+
+ if err != nil {+ t.Fatalf("Failed to build sites: %s", err)+ }
+
+ _, err = hugofs.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, 4)
+ require.Len(t, frSite.regularPages, 3)
+
+ // Verify translations
+ assertFileContent(t, "public/en/sect/doc1-slug/index.html", true, "Hello")
+ assertFileContent(t, "public/fr/sect/doc1/index.html", true, "Bonjour")
+
+ // check single page content
+ assertFileContent(t, "public/fr/sect/doc1/index.html", true, "Single", "Shortcode: Bonjour")
+ assertFileContent(t, "public/en/sect/doc1-slug/index.html", true, "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
+ {+ nil,
+ []fsnotify.Event{{Name: "content/sect/doc2.en.md", Op: fsnotify.Remove}},+ func(t *testing.T) {+ require.Len(t, enSite.regularPages, 3, "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, "new_en_1", "2016-07-31", "content/new1.en.md", -5)
+ writeNewContentFile(t, "new_en_2", "1989-07-30", "content/new2.en.md", -10)
+ writeNewContentFile(t, "new_fr_1", "2016-07-30", "content/new1.fr.md", 10)
+ },
+ []fsnotify.Event{+ {Name: "content/new1.en.md", Op: fsnotify.Create},+ {Name: "content/new2.en.md", Op: fsnotify.Create},+ {Name: "content/new1.fr.md", Op: fsnotify.Create},+ },
+ func(t *testing.T) {+ require.Len(t, enSite.regularPages, 5)
+ require.Len(t, enSite.AllPages, 30)
+ require.Len(t, frSite.regularPages, 4)
+ 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, "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, p)
+ doc1 += "CHANGED"
+ writeSource(t, p, doc1)
+ },
+ []fsnotify.Event{{Name: "content/sect/doc1.en.md", Op: fsnotify.Write}},+ func(t *testing.T) {+ require.Len(t, enSite.regularPages, 5)
+ doc1 := readDestination(t, "public/en/sect/doc1-slug/index.html")
+ require.True(t, strings.Contains(doc1, "CHANGED"), doc1)
+
+ },
+ },
+ // Rename a file
+ {+ func(t *testing.T) {+ if err := hugofs.Source().Rename("content/new1.en.md", "content/new1renamed.en.md"); err != nil {+ t.Fatalf("Rename failed: %s", err)+ }
+ },
+ []fsnotify.Event{+ {Name: "content/new1renamed.en.md", Op: fsnotify.Rename},+ {Name: "content/new1.en.md", Op: fsnotify.Rename},+ },
+ func(t *testing.T) {+ require.Len(t, enSite.regularPages, 5, "Rename")
+ require.Equal(t, "new_en_1", enSite.regularPages[1].Title)
+ rendered := readDestination(t, "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, template)
+ templateContent += "{{ print \"Template Changed\"}}"+ writeSource(t, template, templateContent)
+ },
+ []fsnotify.Event{{Name: "layouts/_default/single.html", Op: fsnotify.Write}},+ func(t *testing.T) {+ require.Len(t, enSite.regularPages, 5)
+ require.Len(t, enSite.AllPages, 30)
+ require.Len(t, frSite.regularPages, 4)
+ doc1 := readDestination(t, "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, languageFile)
+ langContent = strings.Replace(langContent, "Bonjour", "Salut", 1)
+ writeSource(t, languageFile, langContent)
+ },
+ []fsnotify.Event{{Name: "i18n/fr.yaml", Op: fsnotify.Write}},+ func(t *testing.T) {+ require.Len(t, enSite.regularPages, 5)
+ require.Len(t, enSite.AllPages, 30)
+ require.Len(t, frSite.regularPages, 4)
+ docEn := readDestination(t, "public/en/sect/doc1-slug/index.html")
+ require.True(t, strings.Contains(docEn, "Hello"), "No Hello")
+ docFr := readDestination(t, "public/fr/sect/doc1/index.html")
+ require.True(t, strings.Contains(docFr, "Salut"), "No Salut")
+
+ homeEn := enSite.getPage(PageHome)
+ 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, "layouts/shortcodes/shortcode.html", "Modified Shortcode: {{ i18n \"hello\" }}")+ },
+ []fsnotify.Event{+ {Name: "layouts/shortcodes/shortcode.html", Op: fsnotify.Write},+ },
+ func(t *testing.T) {+ require.Len(t, enSite.regularPages, 5)
+ require.Len(t, enSite.AllPages, 30)
+ require.Len(t, frSite.regularPages, 4)
+ assertFileContent(t, "public/fr/sect/doc1/index.html", true, "Single", "Modified Shortcode: Salut")
+ assertFileContent(t, "public/en/sect/doc1-slug/index.html", true, "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.rawContent, []byte("</")), p.BaseFileName()+": "+string(p.rawContent))+ require.Equal(t, p.shouldBuild(), p.Content != "", p.BaseFileName())
+
+ require.Equal(t, p.shouldBuild(), p.Content != "", p.BaseFileName())
+
+ filename := filepath.Join("public", p.TargetPath())+ if strings.HasSuffix(filename, ".html") {+ // TODO(bep) the end result is correct, but it is weird that we cannot use targetPath directly here.
+ filename = strings.Replace(filename, ".html", "/index.html", 1)
+ }
+
+ require.Equal(t, p.shouldBuild(), destinationExists(filename), filename)
+ }
+}
+
+func TestAddNewLanguage(t *testing.T) {+ testCommonResetState()
+ siteConfig := testSiteConfig{DefaultContentLanguage: "fr"}+
+ sites := createMultiTestSites(t, siteConfig, multiSiteTOMLConfigTemplate)
+ cfg := BuildCfg{}+
+ err := sites.Build(cfg)
+
+ if err != nil {+ t.Fatalf("Failed to build sites: %s", err)+ }
+
+ newConfig := multiSiteTOMLConfigTemplate + `
+
+[Languages.sv]
+weight = 15
+title = "Svenska"
+`
+
+ newConfig = createConfig(t, siteConfig, newConfig)
+
+ writeNewContentFile(t, "Swedish Contentfile", "2016-01-01", "content/sect/doc1.sv.md", 10)
+ // replace the config
+ writeSource(t, "multilangconfig.toml", newConfig)
+
+ // Watching does not work with in-memory fs, so we trigger a reload manually
+ require.NoError(t, 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(PageHome)
+ require.NotNil(t, homeEn)
+ require.Len(t, homeEn.Translations(), 4)
+ require.Equal(t, "sv", homeEn.Translations()[0].Lang())
+
+ require.Len(t, enSite.regularPages, 4)
+ require.Len(t, frSite.regularPages, 3)
+
+ // 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())
+
+}
+
+func TestChangeDefaultLanguage(t *testing.T) {+ testCommonResetState()
+ viper.Set("defaultContentLanguageInSubdir", false)+
+ sites := createMultiTestSites(t, testSiteConfig{DefaultContentLanguage: "fr"}, multiSiteTOMLConfigTemplate)+ cfg := BuildCfg{}+
+ err := sites.Build(cfg)
+
+ if err != nil {+ t.Fatalf("Failed to build sites: %s", err)+ }
+
+ assertFileContent(t, "public/sect/doc1/index.html", true, "Single", "Bonjour")
+ assertFileContent(t, "public/en/sect/doc2/index.html", true, "Single", "Hello")
+
+ newConfig := createConfig(t, testSiteConfig{DefaultContentLanguage: "en"}, multiSiteTOMLConfigTemplate)+
+ // replace the config
+ writeSource(t, "multilangconfig.toml", newConfig)
+
+ // Watching does not work with in-memory fs, so we trigger a reload manually
+ require.NoError(t, 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
+ assertFileContent(t, "public/fr/sect/doc1/index.html", true, "Single", "Bonjour")
+ assertFileContent(t, "public/sect/doc2/index.html", true, "Single", "Hello")
+}
+
+func TestTableOfContentsInShortcodes(t *testing.T) {+ testCommonResetState()
+
+ sites := createMultiTestSites(t, testSiteConfig{DefaultContentLanguage: "en"}, multiSiteTOMLConfigTemplate)+
+ writeSource(t, "layouts/shortcodes/toc.html", tocShortcode)
+ writeSource(t, "content/post/simple.en.md", tocPageSimple)
+ writeSource(t, "content/post/withSCInHeading.en.md", tocPageWithShortcodesInHeadings)
+
+ cfg := BuildCfg{}+
+ err := sites.Build(cfg)
+
+ if err != nil {+ t.Fatalf("Failed to build sites: %s", err)+ }
+
+ assertFileContent(t, "public/en/post/simple/index.html", true, tocPageSimpleExpected)
+ assertFileContent(t, "public/en/post/withSCInHeading/index.html", true, 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 = `
+defaultExtension = "html"
+baseURL = "http://example.com/blog"
+disableSitemap = false
+disableRSS = false
+rssURI = "index.xml"
+
+paginate = 1
+defaultContentLanguage = "{{ .DefaultContentLanguage }}"+
+[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 multiSiteYAMLConfig = `
+defaultExtension: "html"
+baseURL: "http://example.com/blog"
+disableSitemap: false
+disableRSS: false
+rssURI: "index.xml"
+
+paginate: 1
+defaultContentLanguage: "fr"
+
+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 multiSiteJSONConfig = `
+{+ "defaultExtension": "html",
+ "baseURL": "http://example.com/blog",
+ "disableSitemap": false,
+ "disableRSS": false,
+ "rssURI": "index.xml",
+ "paginate": 1,
+ "defaultContentLanguage": "fr",
+ "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)
+
+ // Add some layouts
+ if err := afero.WriteFile(hugofs.Source(),
+ 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(hugofs.Source(),
+ filepath.Join("layouts", "_default/list.html"),+ []byte("{{ $p := .Paginator }}List Page {{ $p.PageNumber }}: {{ .Title }}|{{ i18n \"hello\" }}|{{ .Permalink }}"),+ 0755); err != nil {+ t.Fatalf("Failed to write layout file: %s", err)+ }
+
+ if err := afero.WriteFile(hugofs.Source(),
+ filepath.Join("layouts", "index.html"),+ []byte("{{ $p := .Paginator }}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(hugofs.Source(),
+ filepath.Join("layouts", "shortcodes", "shortcode.html"),+ []byte("Shortcode: {{ i18n \"hello\" }}"),+ 0755); err != nil {+ t.Fatalf("Failed to write layout file: %s", err)+ }
+
+ // Add some language files
+ if err := afero.WriteFile(hugofs.Source(),
+ filepath.Join("i18n", "en.yaml"),+ []byte(`
+- id: hello
+ translation: "Hello"
+`),
+ 0755); err != nil {+ t.Fatalf("Failed to write language file: %s", err)+ }
+ if err := afero.WriteFile(hugofs.Source(),
+ filepath.Join("i18n", "fr.yaml"),+ []byte(`
+- id: hello
+ translation: "Bonjour"
+`),
+ 0755); err != nil {+ t.Fatalf("Failed to write language file: %s", err)+ }
+
+ // Sources
+ sources := []source.ByteSource{+ {Name: filepath.FromSlash("root.en.md"), Content: []byte(`---+title: root
+weight: 10000
+slug: root
+publishdate: "2000-01-01"
+---
+# root
+`)},
+ {Name: filepath.FromSlash("sect/doc1.en.md"), Content: []byte(`---+title: doc1
+weight: 1
+slug: doc1-slug
+tags:
+ - tag1
+publishdate: "2000-01-01"
+---
+# doc1
+*some "content"*
+
+{{< shortcode >}}+
+NOTE: slug should be used as URL
+`)},
+ {Name: filepath.FromSlash("sect/doc1.fr.md"), Content: []byte(`---+title: doc1
+weight: 1
+plaques:
+ - frtag1
+ - frtag2
+publishdate: "2000-01-04"
+---
+# doc1
+*quelque "contenu"*
+
+{{< shortcode >}}+
+NOTE: should be in the 'en' Page's 'Translations' field.
+NOTE: date is after "doc3"
+`)},
+ {Name: filepath.FromSlash("sect/doc2.en.md"), Content: []byte(`---+title: doc2
+weight: 2
+publishdate: "2000-01-02"
+---
+# doc2
+*some content*
+NOTE: without slug, "doc2" should be used, without ".en" as URL
+`)},
+ {Name: filepath.FromSlash("sect/doc3.en.md"), Content: []byte(`---+title: doc3
+weight: 3
+publishdate: "2000-01-03"
+tags:
+ - tag2
+ - tag1
+url: /superbob
+---
+# doc3
+*some content*
+NOTE: third 'en' doc, should trigger pagination on home page.
+`)},
+ {Name: filepath.FromSlash("sect/doc4.md"), Content: []byte(`---+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'
+`)},
+ {Name: filepath.FromSlash("other/doc5.fr.md"), Content: []byte(`---+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
+ {Name: filepath.FromSlash("stats/expired.fr.md"), Content: []byte(`---+title: expired
+publishdate: "2000-01-06"
+expiryDate: "2001-01-06"
+---
+# Expired
+`)},
+ {Name: filepath.FromSlash("stats/future.fr.md"), Content: []byte(`---+title: future
+weight: 6
+publishdate: "2100-01-06"
+---
+# Future
+`)},
+ {Name: filepath.FromSlash("stats/expired.en.md"), Content: []byte(`---+title: expired
+weight: 7
+publishdate: "2000-01-06"
+expiryDate: "2001-01-06"
+---
+# Expired
+`)},
+ {Name: filepath.FromSlash("stats/future.en.md"), Content: []byte(`---+title: future
+weight: 6
+publishdate: "2100-01-06"
+---
+# Future
+`)},
+ {Name: filepath.FromSlash("stats/draft.en.md"), Content: []byte(`---+title: expired
+publishdate: "2000-01-06"
+draft: true
+---
+# Draft
+`)},
+ {Name: filepath.FromSlash("stats/tax.nn.md"), Content: []byte(`---+title: Tax NN
+weight: 8
+publishdate: "2000-01-06"
+weight: 1001
+lag:
+- Sogndal
+---
+# Tax NN
+`)},
+ {Name: filepath.FromSlash("stats/tax.nb.md"), Content: []byte(`---+title: Tax NB
+weight: 8
+publishdate: "2000-01-06"
+weight: 1002
+lag:
+- Sogndal
+---
+# Tax NB
+`)},
+ }
+
+ configFile := "multilangconfig." + configSuffix
+ writeSource(t, configFile, configContent)
+ if err := LoadGlobalConfig("", configFile); err != nil {+ t.Fatalf("Failed to load config: %s", err)+ }
+
+ // Hugo support using ByteSource's directly (for testing),
+ // but to make it more real, we write them to the mem file system.
+ for _, s := range sources {+ if err := afero.WriteFile(hugofs.Source(), filepath.Join("content", s.Name), s.Content, 0755); err != nil {+ t.Fatalf("Failed to write file: %s", err)+ }
+ }
+
+ // Add some data
+ writeSource(t, "data/hugo.toml", "slogan = \"Hugo Rocks!\"")
+
+ sites, err := NewHugoSitesFromConfiguration()
+
+ if err != nil {+ t.Fatalf("Failed to create sites: %s", err)+ }
+
+ if len(sites.Sites) != 4 {+ t.Fatalf("Got %d sites", len(sites.Sites))+ }
+
+ return sites
+}
+
+func writeSource(t *testing.T, filename, content string) {+ if err := afero.WriteFile(hugofs.Source(), filepath.FromSlash(filename), []byte(content), 0755); err != nil {+ t.Fatalf("Failed to write file: %s", err)+ }
+}
+
+func readDestination(t *testing.T, filename string) string {+ return readFileFromFs(t, hugofs.Destination(), filename)
+}
+
+func destinationExists(filename string) bool {+ b, err := helpers.Exists(filename, hugofs.Destination())
+ if err != nil {+ panic(err)
+ }
+ return b
+}
+
+func readSource(t *testing.T, filename string) string {+ return readFileFromFs(t, hugofs.Source(), filename)
+}
+
+func readFileFromFs(t *testing.T, 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, title, date, filename string, weight int) {+ content := newTestPage(title, date, weight)
+ writeSource(t, 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()
+}
--- a/hugolib/hugo_sites_test.go
+++ /dev/null
@@ -1,1266 +1,0 @@
-package hugolib
-
-import (
- "bytes"
- "fmt"
- "regexp"
- "strings"
- "testing"
-
- "os"
- "path/filepath"
- "text/template"
-
- "github.com/fortytw2/leaktest"
- "github.com/fsnotify/fsnotify"
- "github.com/spf13/afero"
- "github.com/spf13/hugo/helpers"
- "github.com/spf13/hugo/hugofs"
- "github.com/spf13/hugo/source"
- // jww "github.com/spf13/jwalterweatherman"
- "github.com/spf13/viper"
- "github.com/stretchr/testify/assert"
- "github.com/stretchr/testify/require"
-)
-
-type testSiteConfig struct {- DefaultContentLanguage string
-}
-
-func init() {- nodePageFeatureFlag = true
- testCommonResetState()
-}
-
-func testCommonResetState() {- hugofs.InitMemFs()
- viper.Reset()
- viper.SetFs(hugofs.Source())
- helpers.ResetConfigProvider()
- loadDefaultSettings()
-
- // Default is false, but true is easier to use as default in tests
- viper.Set("defaultContentLanguageInSubdir", true)-
- if err := hugofs.Source().Mkdir("content", 0755); err != nil {- panic("Content folder creation failed.")- }
-
-}
-
-func TestMultiSitesMainLangInRoot(t *testing.T) {- //jww.SetStdoutThreshold(jww.LevelDebug)
-
- for _, b := range []bool{true, false} {- doTestMultiSitesMainLangInRoot(t, b)
- }
-}
-
-func doTestMultiSitesMainLangInRoot(t *testing.T, defaultInSubDir bool) {- testCommonResetState()
- viper.Set("defaultContentLanguageInSubdir", defaultInSubDir)- siteConfig := testSiteConfig{DefaultContentLanguage: "fr"}-
- sites := createMultiTestSites(t, siteConfig, multiSiteTOMLConfigTemplate)
-
- 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.Info.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, replaceDefaultContentLanguageValue("http://example.com/blog/fr/sect/doc1/", defaultInSubDir), frPerm)- require.Equal(t, replaceDefaultContentLanguageValue("/blog/fr/sect/doc1/", defaultInSubDir), frRelPerm)-
- assertFileContent(t, "public/fr/sect/doc1/index.html", defaultInSubDir, "Single", "Bonjour")
- assertFileContent(t, "public/en/sect/doc1-slug/index.html", defaultInSubDir, "Single", "Hello")
-
- // Check home
- if defaultInSubDir {- // should have a redirect on top level.
- assertFileContent(t, "public/index.html", true, `<meta http-equiv="refresh" content="0; url=http://example.com/blog/fr" />`)
- } else {- // should have redirect back to root
- assertFileContent(t, "public/fr/index.html", true, `<meta http-equiv="refresh" content="0; url=http://example.com/blog" />`)
- }
- assertFileContent(t, "public/fr/index.html", defaultInSubDir, "Home", "Bonjour")
- assertFileContent(t, "public/en/index.html", defaultInSubDir, "Home", "Hello")
-
- // Check list pages
- assertFileContent(t, "public/fr/sect/index.html", defaultInSubDir, "List", "Bonjour")
- assertFileContent(t, "public/en/sect/index.html", defaultInSubDir, "List", "Hello")
- assertFileContent(t, "public/fr/plaques/frtag1/index.html", defaultInSubDir, "List", "Bonjour")
- assertFileContent(t, "public/en/tags/tag1/index.html", defaultInSubDir, "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.
- assertFileContent(t, "public/sitemap.xml", true,
- "<loc>http://example.com/blog/en/sitemap.xml</loc>",
- "<loc>http://example.com/blog/fr/sitemap.xml</loc>")
-
- if defaultInSubDir {- assertFileContent(t, "public/fr/sitemap.xml", true, "<loc>http://example.com/blog/fr/</loc>")
- } else {- assertFileContent(t, "public/fr/sitemap.xml", true, "<loc>http://example.com/blog/</loc>")
- }
- assertFileContent(t, "public/en/sitemap.xml", true, "<loc>http://example.com/blog/en/</loc>")
-
- // Check rss
- assertFileContent(t, "public/fr/index.xml", defaultInSubDir, `<atom:link href="http://example.com/blog/fr/index.xml"`)
- assertFileContent(t, "public/en/index.xml", defaultInSubDir, `<atom:link href="http://example.com/blog/en/index.xml"`)
- assertFileContent(t, "public/fr/sect/index.xml", defaultInSubDir, `<atom:link href="http://example.com/blog/fr/sect/index.xml"`)
- assertFileContent(t, "public/en/sect/index.xml", defaultInSubDir, `<atom:link href="http://example.com/blog/en/sect/index.xml"`)
- assertFileContent(t, "public/fr/plaques/frtag1/index.xml", defaultInSubDir, `<atom:link href="http://example.com/blog/fr/plaques/frtag1/index.xml"`)
- assertFileContent(t, "public/en/tags/tag1/index.xml", defaultInSubDir, `<atom:link href="http://example.com/blog/en/tags/tag1/index.xml"`)
-
- // Check paginators
- assertFileContent(t, "public/fr/page/1/index.html", defaultInSubDir, `refresh" content="0; url=http://example.com/blog/fr/"`)
- assertFileContent(t, "public/en/page/1/index.html", defaultInSubDir, `refresh" content="0; url=http://example.com/blog/en/"`)
- assertFileContent(t, "public/fr/page/2/index.html", defaultInSubDir, "Home Page 2", "Bonjour", "http://example.com/blog/fr/")
- assertFileContent(t, "public/en/page/2/index.html", defaultInSubDir, "Home Page 2", "Hello", "http://example.com/blog/en/")
- assertFileContent(t, "public/fr/sect/page/1/index.html", defaultInSubDir, `refresh" content="0; url=http://example.com/blog/fr/sect/"`)
- assertFileContent(t, "public/en/sect/page/1/index.html", defaultInSubDir, `refresh" content="0; url=http://example.com/blog/en/sect/"`)
- assertFileContent(t, "public/fr/sect/page/2/index.html", defaultInSubDir, "List Page 2", "Bonjour", "http://example.com/blog/fr/sect/")
- assertFileContent(t, "public/en/sect/page/2/index.html", defaultInSubDir, "List Page 2", "Hello", "http://example.com/blog/en/sect/")
- assertFileContent(t, "public/fr/plaques/frtag1/page/1/index.html", defaultInSubDir, `refresh" content="0; url=http://example.com/blog/fr/plaques/frtag1/"`)
- assertFileContent(t, "public/en/tags/tag1/page/1/index.html", defaultInSubDir, `refresh" content="0; url=http://example.com/blog/en/tags/tag1/"`)
- assertFileContent(t, "public/fr/plaques/frtag1/page/2/index.html", defaultInSubDir, "List Page 2", "Bonjour", "http://example.com/blog/fr/plaques/frtag1/")
- assertFileContent(t, "public/en/tags/tag1/page/2/index.html", defaultInSubDir, "List Page 2", "Hello", "http://example.com/blog/en/tags/tag1/")
- // nn (Nynorsk) and nb (Bokmål) have custom pagePath: side ("page" in Norwegian)- assertFileContent(t, "public/nn/side/1/index.html", defaultInSubDir, `refresh" content="0; url=http://example.com/blog/nn/"`)
- assertFileContent(t, "public/nb/side/1/index.html", defaultInSubDir, `refresh" content="0; url=http://example.com/blog/nb/"`)
-}
-
-func replaceDefaultContentLanguageValue(value string, defaultInSubDir bool) string {- replace := viper.GetString("defaultContentLanguage") + "/"- if !defaultInSubDir {- value = strings.Replace(value, replace, "", 1)
-
- }
- return value
-
-}
-
-func assertFileContent(t *testing.T, filename string, defaultInSubDir bool, matches ...string) {- filename = replaceDefaultContentLanguageValue(filename, defaultInSubDir)
- content := readDestination(t, filename)
- for _, match := range matches {- match = replaceDefaultContentLanguageValue(match, defaultInSubDir)
- require.True(t, strings.Contains(content, match), fmt.Sprintf("File no match for\n%q in\n%q:\n%s", match, filename, content))- }
-}
-
-func assertFileContentRegexp(t *testing.T, filename string, defaultInSubDir bool, matches ...string) {- filename = replaceDefaultContentLanguageValue(filename, defaultInSubDir)
- content := readDestination(t, filename)
- for _, match := range matches {- match = replaceDefaultContentLanguageValue(match, defaultInSubDir)
- r := regexp.MustCompile(match)
- require.True(t, r.MatchString(content), fmt.Sprintf("File no match for\n%q in\n%q:\n%s", match, filename, content))- }
-}
-
-//
-func TestMultiSitesBuild(t *testing.T) {- for _, config := range []struct {- content string
- suffix string
- }{- {multiSiteTOMLConfigTemplate, "toml"},- {multiSiteYAMLConfig, "yml"},- {multiSiteJSONConfig, "json"},- } {- doTestMultiSitesBuild(t, config.content, config.suffix)
- }
-}
-
-func doTestMultiSitesBuild(t *testing.T, configTemplate, configSuffix string) {- defer leaktest.Check(t)()
- testCommonResetState()
- siteConfig := testSiteConfig{DefaultContentLanguage: "fr"}- sites := createMultiTestSitesForConfig(t, siteConfig, configTemplate, configSuffix)
-
- err := sites.Build(BuildCfg{})-
- if err != nil {- t.Fatalf("Failed to build sites: %s", err)- }
-
- enSite := sites.Sites[0]
-
- assert.Equal(t, "en", enSite.Language.Lang)
-
- if len(enSite.regularPages) != 4 {- t.Fatal("Expected 4 english pages")- }
- assert.Len(t, enSite.Source.Files(), 14, "should have 13 source files")
- assert.Len(t, enSite.AllPages, 28, "should have 28 total pages (including translations and index types)")
-
- doc1en := enSite.regularPages[0]
- permalink, err := doc1en.Permalink()
- assert.NoError(t, err, "permalink call failed")
- assert.Equal(t, "http://example.com/blog/en/sect/doc1-slug/", permalink, "invalid doc1.en permalink")
- assert.Len(t, doc1en.Translations(), 1, "doc1-en should have one translation, excluding itself")
-
- doc2 := enSite.regularPages[1]
- permalink, err = doc2.Permalink()
- assert.NoError(t, err, "permalink call failed")
- assert.Equal(t, "http://example.com/blog/en/sect/doc2/", permalink, "invalid doc2 permalink")
-
- doc3 := enSite.regularPages[2]
- permalink, err = doc3.Permalink()
- assert.NoError(t, err, "permalink call failed")
- // 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.
- assert.Equal(t, "http://example.com/blog/superbob", permalink, "invalid doc3 permalink")
-
- assert.Equal(t, "/superbob", doc3.URL(), "invalid url, was specified on doc3")
- assertFileContent(t, "public/superbob/index.html", true, "doc3|Hello|en")
- assert.Equal(t, doc2.Next, doc3, "doc3 should follow doc2, in .Next")
-
- doc1fr := doc1en.Translations()[0]
- permalink, err = doc1fr.Permalink()
- assert.NoError(t, err, "permalink call failed")
- assert.Equal(t, "http://example.com/blog/fr/sect/doc1/", permalink, "invalid doc1fr permalink")
-
- assert.Equal(t, doc1en.Translations()[0], doc1fr, "doc1-en should have doc1-fr as translation")
- assert.Equal(t, doc1fr.Translations()[0], doc1en, "doc1-fr should have doc1-en as translation")
- assert.Equal(t, "fr", doc1fr.Language().Lang)
-
- doc4 := enSite.AllPages[4]
- permalink, err = doc4.Permalink()
- assert.NoError(t, err, "permalink call failed")
- assert.Equal(t, "http://example.com/blog/fr/sect/doc4/", permalink, "invalid doc4 permalink")
- assert.Equal(t, "/blog/fr/sect/doc4/", doc4.URL())
-
- assert.Len(t, doc4.Translations(), 0, "found translations for doc4")
-
- doc5 := enSite.AllPages[5]
- permalink, err = doc5.Permalink()
- assert.NoError(t, err, "permalink call failed")
- assert.Equal(t, "http://example.com/blog/fr/somewhere/else/doc5", permalink, "invalid doc5 permalink")
-
- // Taxonomies and their URLs
- assert.Len(t, enSite.Taxonomies, 1, "should have 1 taxonomy")
- tags := enSite.Taxonomies["tags"]
- assert.Len(t, tags, 2, "should have 2 different tags")
- assert.Equal(t, tags["tag1"][0].Page, doc1en, "first tag1 page should be doc1")
-
- frSite := sites.Sites[1]
-
- assert.Equal(t, "fr", frSite.Language.Lang)
- assert.Len(t, frSite.regularPages, 3, "should have 3 pages")
- assert.Len(t, frSite.AllPages, 28, "should have 28 total pages (including translations and nodes)")
-
- for _, frenchPage := range frSite.regularPages {- assert.Equal(t, "fr", frenchPage.Lang())
- }
-
- // Check redirect to main language, French
- languageRedirect := readDestination(t, "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)
- assertFileContent(t, "public/en/index.html", true, "Home Page 1", "Hello", "Hugo Rocks!")
- assertFileContent(t, "public/fr/index.html", true, "Home Page 1", "Bonjour", "Hugo Rocks!")
-
- // check single page content
- assertFileContent(t, "public/fr/sect/doc1/index.html", true, "Single", "Shortcode: Bonjour")
- assertFileContent(t, "public/en/sect/doc1-slug/index.html", true, "Single", "Shortcode: Hello")
-
- // Check node translations
- homeEn := enSite.getPage(PageHome)
- 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(PageSection, "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(PageTaxonomyTerm, "lag")
- require.NotNil(t, taxNn)
- require.Len(t, taxNn.Translations(), 1)
- require.Equal(t, "nb", taxNn.Translations()[0].Lang())
-
- taxTermNn := nnSite.getPage(PageTaxonomy, "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, "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, "public/en/sitemap.xml")
- sitemapFr := readDestination(t, "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, "public/fr/plaques/frtag1/index.html")
- readDestination(t, "public/en/tags/tag1/index.html")
-
- // Check Blackfriday config
- assert.True(t, strings.Contains(string(doc1fr.Content), "«"), string(doc1fr.Content))
- assert.False(t, strings.Contains(string(doc1en.Content), "«"), string(doc1en.Content))
- assert.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)
-
-}
-
-func TestMultiSitesRebuild(t *testing.T) {-
- defer leaktest.Check(t)()
- testCommonResetState()
- siteConfig := testSiteConfig{DefaultContentLanguage: "fr"}- sites := createMultiTestSites(t, siteConfig, multiSiteTOMLConfigTemplate)
- cfg := BuildCfg{Watching: true}-
- err := sites.Build(cfg)
-
- if err != nil {- t.Fatalf("Failed to build sites: %s", err)- }
-
- _, err = hugofs.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, 4)
- require.Len(t, frSite.regularPages, 3)
-
- // Verify translations
- assertFileContent(t, "public/en/sect/doc1-slug/index.html", true, "Hello")
- assertFileContent(t, "public/fr/sect/doc1/index.html", true, "Bonjour")
-
- // check single page content
- assertFileContent(t, "public/fr/sect/doc1/index.html", true, "Single", "Shortcode: Bonjour")
- assertFileContent(t, "public/en/sect/doc1-slug/index.html", true, "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
- {- nil,
- []fsnotify.Event{{Name: "content/sect/doc2.en.md", Op: fsnotify.Remove}},- func(t *testing.T) {- require.Len(t, enSite.regularPages, 3, "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, "new_en_1", "2016-07-31", "content/new1.en.md", -5)
- writeNewContentFile(t, "new_en_2", "1989-07-30", "content/new2.en.md", -10)
- writeNewContentFile(t, "new_fr_1", "2016-07-30", "content/new1.fr.md", 10)
- },
- []fsnotify.Event{- {Name: "content/new1.en.md", Op: fsnotify.Create},- {Name: "content/new2.en.md", Op: fsnotify.Create},- {Name: "content/new1.fr.md", Op: fsnotify.Create},- },
- func(t *testing.T) {- require.Len(t, enSite.regularPages, 5)
- require.Len(t, enSite.AllPages, 30)
- require.Len(t, frSite.regularPages, 4)
- 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, "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, p)
- doc1 += "CHANGED"
- writeSource(t, p, doc1)
- },
- []fsnotify.Event{{Name: "content/sect/doc1.en.md", Op: fsnotify.Write}},- func(t *testing.T) {- require.Len(t, enSite.regularPages, 5)
- doc1 := readDestination(t, "public/en/sect/doc1-slug/index.html")
- require.True(t, strings.Contains(doc1, "CHANGED"), doc1)
-
- },
- },
- // Rename a file
- {- func(t *testing.T) {- if err := hugofs.Source().Rename("content/new1.en.md", "content/new1renamed.en.md"); err != nil {- t.Fatalf("Rename failed: %s", err)- }
- },
- []fsnotify.Event{- {Name: "content/new1renamed.en.md", Op: fsnotify.Rename},- {Name: "content/new1.en.md", Op: fsnotify.Rename},- },
- func(t *testing.T) {- require.Len(t, enSite.regularPages, 5, "Rename")
- require.Equal(t, "new_en_1", enSite.regularPages[1].Title)
- rendered := readDestination(t, "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, template)
- templateContent += "{{ print \"Template Changed\"}}"- writeSource(t, template, templateContent)
- },
- []fsnotify.Event{{Name: "layouts/_default/single.html", Op: fsnotify.Write}},- func(t *testing.T) {- require.Len(t, enSite.regularPages, 5)
- require.Len(t, enSite.AllPages, 30)
- require.Len(t, frSite.regularPages, 4)
- doc1 := readDestination(t, "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, languageFile)
- langContent = strings.Replace(langContent, "Bonjour", "Salut", 1)
- writeSource(t, languageFile, langContent)
- },
- []fsnotify.Event{{Name: "i18n/fr.yaml", Op: fsnotify.Write}},- func(t *testing.T) {- require.Len(t, enSite.regularPages, 5)
- require.Len(t, enSite.AllPages, 30)
- require.Len(t, frSite.regularPages, 4)
- docEn := readDestination(t, "public/en/sect/doc1-slug/index.html")
- require.True(t, strings.Contains(docEn, "Hello"), "No Hello")
- docFr := readDestination(t, "public/fr/sect/doc1/index.html")
- require.True(t, strings.Contains(docFr, "Salut"), "No Salut")
-
- homeEn := enSite.getPage(PageHome)
- 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, "layouts/shortcodes/shortcode.html", "Modified Shortcode: {{ i18n \"hello\" }}")- },
- []fsnotify.Event{- {Name: "layouts/shortcodes/shortcode.html", Op: fsnotify.Write},- },
- func(t *testing.T) {- require.Len(t, enSite.regularPages, 5)
- require.Len(t, enSite.AllPages, 30)
- require.Len(t, frSite.regularPages, 4)
- assertFileContent(t, "public/fr/sect/doc1/index.html", true, "Single", "Modified Shortcode: Salut")
- assertFileContent(t, "public/en/sect/doc1-slug/index.html", true, "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.rawContent, []byte("</")), p.BaseFileName()+": "+string(p.rawContent))- require.Equal(t, p.shouldBuild(), p.Content != "", p.BaseFileName())
-
- require.Equal(t, p.shouldBuild(), p.Content != "", p.BaseFileName())
-
- filename := filepath.Join("public", p.TargetPath())- if strings.HasSuffix(filename, ".html") {- // TODO(bep) the end result is correct, but it is weird that we cannot use targetPath directly here.
- filename = strings.Replace(filename, ".html", "/index.html", 1)
- }
-
- require.Equal(t, p.shouldBuild(), destinationExists(filename), filename)
- }
-}
-
-func TestAddNewLanguage(t *testing.T) {- testCommonResetState()
- siteConfig := testSiteConfig{DefaultContentLanguage: "fr"}-
- sites := createMultiTestSites(t, siteConfig, multiSiteTOMLConfigTemplate)
- cfg := BuildCfg{}-
- err := sites.Build(cfg)
-
- if err != nil {- t.Fatalf("Failed to build sites: %s", err)- }
-
- newConfig := multiSiteTOMLConfigTemplate + `
-
-[Languages.sv]
-weight = 15
-title = "Svenska"
-`
-
- newConfig = createConfig(t, siteConfig, newConfig)
-
- writeNewContentFile(t, "Swedish Contentfile", "2016-01-01", "content/sect/doc1.sv.md", 10)
- // replace the config
- writeSource(t, "multilangconfig.toml", newConfig)
-
- // Watching does not work with in-memory fs, so we trigger a reload manually
- require.NoError(t, 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(PageHome)
- require.NotNil(t, homeEn)
- require.Len(t, homeEn.Translations(), 4)
- require.Equal(t, "sv", homeEn.Translations()[0].Lang())
-
- require.Len(t, enSite.regularPages, 4)
- require.Len(t, frSite.regularPages, 3)
-
- // 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())
-
-}
-
-func TestChangeDefaultLanguage(t *testing.T) {- testCommonResetState()
- viper.Set("defaultContentLanguageInSubdir", false)-
- sites := createMultiTestSites(t, testSiteConfig{DefaultContentLanguage: "fr"}, multiSiteTOMLConfigTemplate)- cfg := BuildCfg{}-
- err := sites.Build(cfg)
-
- if err != nil {- t.Fatalf("Failed to build sites: %s", err)- }
-
- assertFileContent(t, "public/sect/doc1/index.html", true, "Single", "Bonjour")
- assertFileContent(t, "public/en/sect/doc2/index.html", true, "Single", "Hello")
-
- newConfig := createConfig(t, testSiteConfig{DefaultContentLanguage: "en"}, multiSiteTOMLConfigTemplate)-
- // replace the config
- writeSource(t, "multilangconfig.toml", newConfig)
-
- // Watching does not work with in-memory fs, so we trigger a reload manually
- require.NoError(t, 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
- assertFileContent(t, "public/fr/sect/doc1/index.html", true, "Single", "Bonjour")
- assertFileContent(t, "public/sect/doc2/index.html", true, "Single", "Hello")
-}
-
-func TestTableOfContentsInShortcodes(t *testing.T) {- testCommonResetState()
-
- sites := createMultiTestSites(t, testSiteConfig{DefaultContentLanguage: "en"}, multiSiteTOMLConfigTemplate)-
- writeSource(t, "layouts/shortcodes/toc.html", tocShortcode)
- writeSource(t, "content/post/simple.en.md", tocPageSimple)
- writeSource(t, "content/post/withSCInHeading.en.md", tocPageWithShortcodesInHeadings)
-
- cfg := BuildCfg{}-
- err := sites.Build(cfg)
-
- if err != nil {- t.Fatalf("Failed to build sites: %s", err)- }
-
- assertFileContent(t, "public/en/post/simple/index.html", true, tocPageSimpleExpected)
- assertFileContent(t, "public/en/post/withSCInHeading/index.html", true, 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 = `
-defaultExtension = "html"
-baseURL = "http://example.com/blog"
-disableSitemap = false
-disableRSS = false
-rssURI = "index.xml"
-
-paginate = 1
-defaultContentLanguage = "{{ .DefaultContentLanguage }}"-
-[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 multiSiteYAMLConfig = `
-defaultExtension: "html"
-baseURL: "http://example.com/blog"
-disableSitemap: false
-disableRSS: false
-rssURI: "index.xml"
-
-paginate: 1
-defaultContentLanguage: "fr"
-
-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 multiSiteJSONConfig = `
-{- "defaultExtension": "html",
- "baseURL": "http://example.com/blog",
- "disableSitemap": false,
- "disableRSS": false,
- "rssURI": "index.xml",
- "paginate": 1,
- "defaultContentLanguage": "fr",
- "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)
-
- // Add some layouts
- if err := afero.WriteFile(hugofs.Source(),
- 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(hugofs.Source(),
- filepath.Join("layouts", "_default/list.html"),- []byte("{{ $p := .Paginator }}List Page {{ $p.PageNumber }}: {{ .Title }}|{{ i18n \"hello\" }}|{{ .Permalink }}"),- 0755); err != nil {- t.Fatalf("Failed to write layout file: %s", err)- }
-
- if err := afero.WriteFile(hugofs.Source(),
- filepath.Join("layouts", "index.html"),- []byte("{{ $p := .Paginator }}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(hugofs.Source(),
- filepath.Join("layouts", "shortcodes", "shortcode.html"),- []byte("Shortcode: {{ i18n \"hello\" }}"),- 0755); err != nil {- t.Fatalf("Failed to write layout file: %s", err)- }
-
- // Add some language files
- if err := afero.WriteFile(hugofs.Source(),
- filepath.Join("i18n", "en.yaml"),- []byte(`
-- id: hello
- translation: "Hello"
-`),
- 0755); err != nil {- t.Fatalf("Failed to write language file: %s", err)- }
- if err := afero.WriteFile(hugofs.Source(),
- filepath.Join("i18n", "fr.yaml"),- []byte(`
-- id: hello
- translation: "Bonjour"
-`),
- 0755); err != nil {- t.Fatalf("Failed to write language file: %s", err)- }
-
- // Sources
- sources := []source.ByteSource{- {Name: filepath.FromSlash("root.en.md"), Content: []byte(`----title: root
-weight: 10000
-slug: root
-publishdate: "2000-01-01"
----
-# root
-`)},
- {Name: filepath.FromSlash("sect/doc1.en.md"), Content: []byte(`----title: doc1
-weight: 1
-slug: doc1-slug
-tags:
- - tag1
-publishdate: "2000-01-01"
----
-# doc1
-*some "content"*
-
-{{< shortcode >}}-
-NOTE: slug should be used as URL
-`)},
- {Name: filepath.FromSlash("sect/doc1.fr.md"), Content: []byte(`----title: doc1
-weight: 1
-plaques:
- - frtag1
- - frtag2
-publishdate: "2000-01-04"
----
-# doc1
-*quelque "contenu"*
-
-{{< shortcode >}}-
-NOTE: should be in the 'en' Page's 'Translations' field.
-NOTE: date is after "doc3"
-`)},
- {Name: filepath.FromSlash("sect/doc2.en.md"), Content: []byte(`----title: doc2
-weight: 2
-publishdate: "2000-01-02"
----
-# doc2
-*some content*
-NOTE: without slug, "doc2" should be used, without ".en" as URL
-`)},
- {Name: filepath.FromSlash("sect/doc3.en.md"), Content: []byte(`----title: doc3
-weight: 3
-publishdate: "2000-01-03"
-tags:
- - tag2
- - tag1
-url: /superbob
----
-# doc3
-*some content*
-NOTE: third 'en' doc, should trigger pagination on home page.
-`)},
- {Name: filepath.FromSlash("sect/doc4.md"), Content: []byte(`----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'
-`)},
- {Name: filepath.FromSlash("other/doc5.fr.md"), Content: []byte(`----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
- {Name: filepath.FromSlash("stats/expired.fr.md"), Content: []byte(`----title: expired
-publishdate: "2000-01-06"
-expiryDate: "2001-01-06"
----
-# Expired
-`)},
- {Name: filepath.FromSlash("stats/future.fr.md"), Content: []byte(`----title: future
-weight: 6
-publishdate: "2100-01-06"
----
-# Future
-`)},
- {Name: filepath.FromSlash("stats/expired.en.md"), Content: []byte(`----title: expired
-weight: 7
-publishdate: "2000-01-06"
-expiryDate: "2001-01-06"
----
-# Expired
-`)},
- {Name: filepath.FromSlash("stats/future.en.md"), Content: []byte(`----title: future
-weight: 6
-publishdate: "2100-01-06"
----
-# Future
-`)},
- {Name: filepath.FromSlash("stats/draft.en.md"), Content: []byte(`----title: expired
-publishdate: "2000-01-06"
-draft: true
----
-# Draft
-`)},
- {Name: filepath.FromSlash("stats/tax.nn.md"), Content: []byte(`----title: Tax NN
-weight: 8
-publishdate: "2000-01-06"
-weight: 1001
-lag:
-- Sogndal
----
-# Tax NN
-`)},
- {Name: filepath.FromSlash("stats/tax.nb.md"), Content: []byte(`----title: Tax NB
-weight: 8
-publishdate: "2000-01-06"
-weight: 1002
-lag:
-- Sogndal
----
-# Tax NB
-`)},
- }
-
- configFile := "multilangconfig." + configSuffix
- writeSource(t, configFile, configContent)
- if err := LoadGlobalConfig("", configFile); err != nil {- t.Fatalf("Failed to load config: %s", err)- }
-
- // Hugo support using ByteSource's directly (for testing),
- // but to make it more real, we write them to the mem file system.
- for _, s := range sources {- if err := afero.WriteFile(hugofs.Source(), filepath.Join("content", s.Name), s.Content, 0755); err != nil {- t.Fatalf("Failed to write file: %s", err)- }
- }
-
- // Add some data
- writeSource(t, "data/hugo.toml", "slogan = \"Hugo Rocks!\"")
-
- sites, err := NewHugoSitesFromConfiguration()
-
- if err != nil {- t.Fatalf("Failed to create sites: %s", err)- }
-
- if len(sites.Sites) != 4 {- t.Fatalf("Got %d sites", len(sites.Sites))- }
-
- return sites
-}
-
-func writeSource(t *testing.T, filename, content string) {- if err := afero.WriteFile(hugofs.Source(), filepath.FromSlash(filename), []byte(content), 0755); err != nil {- t.Fatalf("Failed to write file: %s", err)- }
-}
-
-func readDestination(t *testing.T, filename string) string {- return readFileFromFs(t, hugofs.Destination(), filename)
-}
-
-func destinationExists(filename string) bool {- b, err := helpers.Exists(filename, hugofs.Destination())
- if err != nil {- panic(err)
- }
- return b
-}
-
-func readSource(t *testing.T, filename string) string {- return readFileFromFs(t, hugofs.Source(), filename)
-}
-
-func readFileFromFs(t *testing.T, 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, title, date, filename string, weight int) {- content := newTestPage(title, date, weight)
- writeSource(t, 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()
-}
--- a/hugolib/node_as_page_test.go
+++ b/hugolib/node_as_page_test.go
@@ -17,6 +17,7 @@
"fmt"
"path/filepath"
"testing"
+ "time"
jww "github.com/spf13/jwalterweatherman"
"github.com/spf13/viper"
@@ -49,22 +50,7 @@
writeLayoutsForNodeAsPageTests(t)
writeNodePagesForNodeAsPageTests("", t)- // Add some regular pages
- for i := 1; i <= 4; i++ {- sect := "sect1"
- if i > 2 {- sect = "sect2"
- }
- writeSource(t, filepath.Join("content", sect, fmt.Sprintf("regular%d.md", i)), fmt.Sprintf(`----title: Page %02d
-categories: [
- "Hugo",
- "Web"
-]
----
-Content Page %02d
-`, i, i))
- }
+ writeRegularPagesForNodeAsPageTests(t)
viper.Set("paginate", 1) viper.Set("title", "Hugo Rocks")@@ -76,41 +62,57 @@
t.Fatalf("Failed to build site: %s", err)}
+ // date order: home, sect1, sect2, cat/hugo, cat/web, categories
+
assertFileContent(t, filepath.Join("public", "index.html"), false,"Index Title: Home Sweet Home!",
"Home <strong>Content!</strong>",
- "# Pages: 9")
+ "# Pages: 9",
+ "Date: 2009-01-02",
+ "Lastmod: 2009-01-03",
+ )
assertFileContent(t, filepath.Join("public", "sect1", "regular1", "index.html"), false, "Single Title: Page 01", "Content Page 01")h := s.owner
- nodes := h.findAllPagesByNodeType(PageHome)
- require.Len(t, nodes, 1)
+ nodes := h.findAllPagesByNodeTypeNotIn(PagePage)
+ require.Len(t, nodes, 6)
- home := nodes[0]
+ home := nodes[5] // oldest
require.True(t, home.IsHome())
require.True(t, home.IsNode())
require.False(t, home.IsPage())
+ section2 := nodes[3]
+ require.Equal(t, "Section2", section2.Title)
+
pages := h.findAllPagesByNodeType(PagePage)
require.Len(t, pages, 4)
first := pages[0]
+
require.False(t, first.IsHome())
require.False(t, first.IsNode())
require.True(t, first.IsPage())
- first.Paginator()
-
// Check Home paginator
assertFileContent(t, filepath.Join("public", "page", "2", "index.html"), false,"Pag: Page 02")
// Check Sections
- assertFileContent(t, filepath.Join("public", "sect1", "index.html"), false, "Section Title: Section", "Section1 <strong>Content!</strong>")- assertFileContent(t, filepath.Join("public", "sect2", "index.html"), false, "Section Title: Section", "Section2 <strong>Content!</strong>")+ assertFileContent(t, filepath.Join("public", "sect1", "index.html"), false,+ "Section Title: Section", "Section1 <strong>Content!</strong>",
+ "Date: 2009-01-04",
+ "Lastmod: 2009-01-05",
+ )
+ assertFileContent(t, filepath.Join("public", "sect2", "index.html"), false,+ "Section Title: Section", "Section2 <strong>Content!</strong>",
+ "Date: 2009-01-06",
+ "Lastmod: 2009-01-07",
+ )
+
// Check Sections paginator
assertFileContent(t, filepath.Join("public", "sect1", "page", "2", "index.html"), false,"Pag: Page 02")
@@ -121,10 +123,17 @@
// Check taxonomy lists
assertFileContent(t, filepath.Join("public", "categories", "hugo", "index.html"), false,- "Taxonomy Title: Taxonomy Hugo", "Taxonomy Hugo <strong>Content!</strong>")
+ "Taxonomy Title: Taxonomy Hugo", "Taxonomy Hugo <strong>Content!</strong>",
+ "Date: 2009-01-08",
+ "Lastmod: 2009-01-09",
+ )
assertFileContent(t, filepath.Join("public", "categories", "web", "index.html"), false,- "Taxonomy Title: Taxonomy Web", "Taxonomy Web <strong>Content!</strong>")
+ "Taxonomy Title: Taxonomy Web",
+ "Taxonomy Web <strong>Content!</strong>",
+ "Date: 2009-01-10",
+ "Lastmod: 2009-01-11",
+ )
// Check taxonomy list paginator
assertFileContent(t, filepath.Join("public", "categories", "hugo", "page", "2", "index.html"), false,@@ -133,7 +142,10 @@
// Check taxonomy terms
assertFileContent(t, filepath.Join("public", "categories", "index.html"), false,- "Taxonomy Terms Title: Taxonomy Term Categories", "Taxonomy Term Categories <strong>Content!</strong>", "k/v: hugo")
+ "Taxonomy Terms Title: Taxonomy Term Categories", "Taxonomy Term Categories <strong>Content!</strong>", "k/v: hugo",
+ "Date: 2009-01-12",
+ "Lastmod: 2009-01-13",
+ )
// There are no pages to paginate over in the taxonomy terms.
@@ -174,21 +186,35 @@
require.Len(t, homePage.Pages, 9) // Alias
assertFileContent(t, filepath.Join("public", "index.html"), false,- "Index Title: Hugo Rocks!")
+ "Index Title: Hugo Rocks!",
+ "Date: 2010-06-12",
+ "Lastmod: 2010-06-13",
+ )
// Taxonomy list
assertFileContent(t, filepath.Join("public", "categories", "hugo", "index.html"), false,- "Taxonomy Title: Hugo")
+ "Taxonomy Title: Hugo",
+ "Date: 2010-06-12",
+ "Lastmod: 2010-06-13",
+ )
// Taxonomy terms
assertFileContent(t, filepath.Join("public", "categories", "index.html"), false,- "Taxonomy Terms Title: Categories")
+ "Taxonomy Terms Title: Categories",
+ )
// Sections
assertFileContent(t, filepath.Join("public", "sect1", "index.html"), false,- "Section Title: Sect1s")
+ "Section Title: Sect1s",
+ "Date: 2010-06-12",
+ "Lastmod: 2010-06-13",
+ )
+
assertFileContent(t, filepath.Join("public", "sect2", "index.html"), false,- "Section Title: Sect2s")
+ "Section Title: Sect2s",
+ "Date: 2008-07-06",
+ "Lastmod: 2008-07-09",
+ )
// RSS
assertFileContent(t, filepath.Join("public", "customrss.xml"), false, "Recent content in Hugo Rocks! on Hugo Rocks!", "<rss")@@ -321,8 +347,8 @@
t.Fatalf("Failed to build site: %s", err)}
- assertFileContent(t, filepath.Join("public", "categories", "hugo", "index.html"), true, "Taxonomy Title: Hugo", "# Pages: 5", "Pag: Home With Taxonomies")- assertFileContent(t, filepath.Join("public", "categories", "home", "index.html"), true, "Taxonomy Title: Home", "# Pages: 1", "Pag: Home With Taxonomies")+ assertFileContent(t, filepath.Join("public", "categories", "hugo", "index.html"), true, "Taxonomy Title: Hugo", "# Pages: 5")+ assertFileContent(t, filepath.Join("public", "categories", "home", "index.html"), true, "Taxonomy Title: Home", "# Pages: 1")}
@@ -415,13 +441,23 @@
langStr = lang + "."
}
+ format := "2006-01-02"
+
+ date, _ := time.Parse(format, "2010-06-15")
+
for i := 1; i <= 4; i++ {sect := "sect1"
if i > 2 {sect = "sect2"
+
+ date, _ = time.Parse(format, "2008-07-15") // Nodes are placed in 2009
+
}
+ date = date.Add(-24 * time.Duration(i) * time.Hour)
writeSource(t, filepath.Join("content", sect, fmt.Sprintf("regular%d.%smd", i, langStr)), fmt.Sprintf(`---title: Page %02d
+lastMod : %q
+date : %q
categories: [
"Hugo",
"Web"
@@ -428,7 +464,7 @@
]
---
Content Page %02d
-`, i, i))
+`, i, date.Add(time.Duration(i)*-24*time.Hour).Format(time.RFC822), date.Add(time.Duration(i)*-2*24*time.Hour).Format(time.RFC822), i))
}
}
@@ -440,41 +476,56 @@
filename = fmt.Sprintf("_index.%s.md", lang)}
- writeSource(t, filepath.Join("content", filename), `---+ format := "2006-01-02"
+
+ date, _ := time.Parse(format, "2009-01-01")
+
+ writeSource(t, filepath.Join("content", filename), fmt.Sprintf(`---title: Home Sweet Home!
+date : %q
+lastMod : %q
---
Home **Content!**
-`)
+`, date.Add(1*24*time.Hour).Format(time.RFC822), date.Add(2*24*time.Hour).Format(time.RFC822)))
- writeSource(t, filepath.Join("content", "sect1", filename), `---+ writeSource(t, filepath.Join("content", "sect1", filename), fmt.Sprintf(`---title: Section1
+date : %q
+lastMod : %q
---
Section1 **Content!**
-`)
-
- writeSource(t, filepath.Join("content", "sect2", filename), `---+`, date.Add(3*24*time.Hour).Format(time.RFC822), date.Add(4*24*time.Hour).Format(time.RFC822)))
+ writeSource(t, filepath.Join("content", "sect2", filename), fmt.Sprintf(`---title: Section2
+date : %q
+lastMod : %q
---
Section2 **Content!**
-`)
+`, date.Add(5*24*time.Hour).Format(time.RFC822), date.Add(6*24*time.Hour).Format(time.RFC822)))
- writeSource(t, filepath.Join("content", "categories", "hugo", filename), `---+ writeSource(t, filepath.Join("content", "categories", "hugo", filename), fmt.Sprintf(`---title: Taxonomy Hugo
+date : %q
+lastMod : %q
---
Taxonomy Hugo **Content!**
-`)
+`, date.Add(7*24*time.Hour).Format(time.RFC822), date.Add(8*24*time.Hour).Format(time.RFC822)))
- writeSource(t, filepath.Join("content", "categories", "web", filename), `---+ writeSource(t, filepath.Join("content", "categories", "web", filename), fmt.Sprintf(`---title: Taxonomy Web
+date : %q
+lastMod : %q
---
Taxonomy Web **Content!**
-`)
+`, date.Add(9*24*time.Hour).Format(time.RFC822), date.Add(10*24*time.Hour).Format(time.RFC822)))
- writeSource(t, filepath.Join("content", "categories", filename), `---+ writeSource(t, filepath.Join("content", "categories", filename), fmt.Sprintf(`---title: Taxonomy Term Categories
+date : %q
+lastMod : %q
---
Taxonomy Term Categories **Content!**
-`)
+`, date.Add(11*24*time.Hour).Format(time.RFC822), date.Add(12*24*time.Hour).Format(time.RFC822)))
}
func writeLayoutsForNodeAsPageTests(t *testing.T) {@@ -490,11 +541,15 @@
Menu Item: {{ .Name }} {{ end }} {{ end }}+Date: {{ .Date.Format "2006-01-02" }}+Lastmod: {{ .Lastmod.Format "2006-01-02" }}`)
writeSource(t, filepath.Join("layouts", "_default", "single.html"), ` Single Title: {{ .Title }} Single Content: {{ .Content }}+Date: {{ .Date.Format "2006-01-02" }}+Lastmod: {{ .Lastmod.Format "2006-01-02" }}`)
writeSource(t, filepath.Join("layouts", "_default", "section.html"), `@@ -504,6 +559,8 @@
{{ range .Paginator.Pages }} Pag: {{ .Title }} {{ end }}+Date: {{ .Date.Format "2006-01-02" }}+Lastmod: {{ .Lastmod.Format "2006-01-02" }}`)
// Taxonomy lists
@@ -514,6 +571,8 @@
{{ range .Paginator.Pages }} Pag: {{ .Title }} {{ end }}+Date: {{ .Date.Format "2006-01-02" }}+Lastmod: {{ .Lastmod.Format "2006-01-02" }}`)
// Taxonomy terms
@@ -523,5 +582,7 @@
{{ range $key, $value := .Data.Terms }} k/v: {{ $key }} / {{ printf "%=v" $value }} {{ end }}+Date: {{ .Date.Format "2006-01-02" }}+Lastmod: {{ .Lastmod.Format "2006-01-02" }}`)
}
--- a/hugolib/page.go
+++ b/hugolib/page.go
@@ -1365,7 +1365,49 @@
p.Data["Pages"] = pages
p.Pages = pages
+ // Now we know enough to set missing dates on home page etc.
+ p.updatePageDates()
+
return nil
+}
+
+func (p *Page) updatePageDates() {+ // TODO(bep) np there is a potential issue with page sorting for home pages
+ // etc. without front matter dates set, but let us wrap the head around
+ // that in another time.
+ if !p.PageType.IsNode() {+ return
+ }
+
+ if !p.Date.IsZero() {+ if p.Lastmod.IsZero() {+ p.Lastmod = p.Date
+ }
+ return
+ } else if !p.Lastmod.IsZero() {+ if p.Date.IsZero() {+ p.Date = p.Lastmod
+ }
+ return
+ }
+
+ // Set it to the first non Zero date in children
+ var foundDate, foundLastMod bool
+
+ for _, child := range p.Pages {+ if !child.Date.IsZero() {+ p.Date = child.Date
+ foundDate = true
+ }
+ if !child.Lastmod.IsZero() {+ p.Lastmod = child.Lastmod
+ foundLastMod = true
+ }
+
+ if foundDate && foundLastMod {+ break
+ }
+ }
}
// Page constains some sync.Once which have a mutex, so we cannot just
--
⑨