ref: 487b210fb8a31b3636030ea960f6565b9e6b3c54
parent: c80308e6b3f3f4f6879ee6b7301575f162ac9209
author: Bjørn Erik Pedersen <[email protected]>
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