diff options
Diffstat (limited to 'hugolib/site_output_test.go')
-rw-r--r-- | hugolib/site_output_test.go | 627 |
1 files changed, 627 insertions, 0 deletions
diff --git a/hugolib/site_output_test.go b/hugolib/site_output_test.go new file mode 100644 index 000000000..232364577 --- /dev/null +++ b/hugolib/site_output_test.go @@ -0,0 +1,627 @@ +// Copyright 2019 The Hugo Authors. All rights reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package hugolib + +import ( + "strings" + "testing" + + qt "github.com/frankban/quicktest" + "github.com/gohugoio/hugo/resources/page" + + "github.com/spf13/afero" + + "fmt" + + "github.com/gohugoio/hugo/helpers" + "github.com/gohugoio/hugo/output" + "github.com/spf13/viper" +) + +func TestSiteWithPageOutputs(t *testing.T) { + for _, outputs := range [][]string{{"html", "json", "calendar"}, {"json"}} { + outputs := outputs + t.Run(fmt.Sprintf("%v", outputs), func(t *testing.T) { + t.Parallel() + doTestSiteWithPageOutputs(t, outputs) + }) + } +} + +func doTestSiteWithPageOutputs(t *testing.T, outputs []string) { + + outputsStr := strings.Replace(fmt.Sprintf("%q", outputs), " ", ", ", -1) + + siteConfig := ` +baseURL = "http://example.com/blog" + +paginate = 1 +defaultContentLanguage = "en" + +disableKinds = ["section", "taxonomy", "taxonomyTerm", "RSS", "sitemap", "robotsTXT", "404"] + +[Taxonomies] +tag = "tags" +category = "categories" + +defaultContentLanguage = "en" + + +[languages] + +[languages.en] +title = "Title in English" +languageName = "English" +weight = 1 + +[languages.nn] +languageName = "Nynorsk" +weight = 2 +title = "Tittel på Nynorsk" + +` + + pageTemplate := `--- +title: "%s" +outputs: %s +--- +# Doc + +{{< myShort >}} + +{{< myOtherShort >}} + +` + + b := newTestSitesBuilder(t).WithConfigFile("toml", siteConfig) + b.WithI18n("en.toml", ` +[elbow] +other = "Elbow" +`, "nn.toml", ` +[elbow] +other = "Olboge" +`) + + b.WithTemplates( + // Case issue partials #3333 + "layouts/partials/GoHugo.html", `Go Hugo Partial`, + "layouts/_default/baseof.json", `START JSON:{{block "main" .}}default content{{ end }}:END JSON`, + "layouts/_default/baseof.html", `START HTML:{{block "main" .}}default content{{ end }}:END HTML`, + "layouts/shortcodes/myOtherShort.html", `OtherShort: {{ "<h1>Hi!</h1>" | safeHTML }}`, + "layouts/shortcodes/myShort.html", `ShortHTML`, + "layouts/shortcodes/myShort.json", `ShortJSON`, + + "layouts/_default/list.json", `{{ define "main" }} +List JSON|{{ .Title }}|{{ .Content }}|Alt formats: {{ len .AlternativeOutputFormats -}}| +{{- range .AlternativeOutputFormats -}} +Alt Output: {{ .Name -}}| +{{- end -}}| +{{- range .OutputFormats -}} +Output/Rel: {{ .Name -}}/{{ .Rel }}|{{ .MediaType }} +{{- end -}} + {{ with .OutputFormats.Get "JSON" }} +<atom:link href={{ .Permalink }} rel="self" type="{{ .MediaType }}" /> +{{ end }} +{{ .Site.Language.Lang }}: {{ T "elbow" -}} +{{ end }} +`, + "layouts/_default/list.html", `{{ define "main" }} +List HTML|{{.Title }}| +{{- with .OutputFormats.Get "HTML" -}} +<atom:link href={{ .Permalink }} rel="self" type="{{ .MediaType }}" /> +{{- end -}} +{{ .Site.Language.Lang }}: {{ T "elbow" -}} +Partial Hugo 1: {{ partial "GoHugo.html" . }} +Partial Hugo 2: {{ partial "GoHugo" . -}} +Content: {{ .Content }} +Len Pages: {{ .Kind }} {{ len .Site.RegularPages }} Page Number: {{ .Paginator.PageNumber }} +{{ end }} +`, + "layouts/_default/single.html", `{{ define "main" }}{{ .Content }}{{ end }}`, + ) + + b.WithContent("_index.md", fmt.Sprintf(pageTemplate, "JSON Home", outputsStr)) + b.WithContent("_index.nn.md", fmt.Sprintf(pageTemplate, "JSON Nynorsk Heim", outputsStr)) + + for i := 1; i <= 10; i++ { + b.WithContent(fmt.Sprintf("p%d.md", i), fmt.Sprintf(pageTemplate, fmt.Sprintf("Page %d", i), outputsStr)) + } + + b.Build(BuildCfg{}) + + s := b.H.Sites[0] + b.Assert(s.language.Lang, qt.Equals, "en") + + home := s.getPage(page.KindHome) + + b.Assert(home, qt.Not(qt.IsNil)) + + lenOut := len(outputs) + + b.Assert(len(home.OutputFormats()), qt.Equals, lenOut) + + // There is currently always a JSON output to make it simpler ... + altFormats := lenOut - 1 + hasHTML := helpers.InStringArray(outputs, "html") + b.AssertFileContent("public/index.json", + "List JSON", + fmt.Sprintf("Alt formats: %d", altFormats), + ) + + if hasHTML { + b.AssertFileContent("public/index.json", + "Alt Output: HTML", + "Output/Rel: JSON/alternate|", + "Output/Rel: HTML/canonical|", + "en: Elbow", + "ShortJSON", + "OtherShort: <h1>Hi!</h1>", + ) + + b.AssertFileContent("public/index.html", + // The HTML entity is a deliberate part of this test: The HTML templates are + // parsed with html/template. + `List HTML|JSON Home|<atom:link href=http://example.com/blog/ rel="self" type="text/html" />`, + "en: Elbow", + "ShortHTML", + "OtherShort: <h1>Hi!</h1>", + "Len Pages: home 10", + ) + b.AssertFileContent("public/page/2/index.html", "Page Number: 2") + b.Assert(b.CheckExists("public/page/2/index.json"), qt.Equals, false) + + b.AssertFileContent("public/nn/index.html", + "List HTML|JSON Nynorsk Heim|", + "nn: Olboge") + } else { + b.AssertFileContent("public/index.json", + "Output/Rel: JSON/canonical|", + // JSON is plain text, so no need to safeHTML this and that + `<atom:link href=http://example.com/blog/index.json rel="self" type="application/json" />`, + "ShortJSON", + "OtherShort: <h1>Hi!</h1>", + ) + b.AssertFileContent("public/nn/index.json", + "List JSON|JSON Nynorsk Heim|", + "nn: Olboge", + "ShortJSON", + ) + } + + of := home.OutputFormats() + + json := of.Get("JSON") + b.Assert(json, qt.Not(qt.IsNil)) + b.Assert(json.RelPermalink(), qt.Equals, "/blog/index.json") + b.Assert(json.Permalink(), qt.Equals, "http://example.com/blog/index.json") + + if helpers.InStringArray(outputs, "cal") { + cal := of.Get("calendar") + b.Assert(cal, qt.Not(qt.IsNil)) + b.Assert(cal.RelPermalink(), qt.Equals, "/blog/index.ics") + b.Assert(cal.Permalink(), qt.Equals, "webcal://example.com/blog/index.ics") + } + + b.Assert(home.HasShortcode("myShort"), qt.Equals, true) + b.Assert(home.HasShortcode("doesNotExist"), qt.Equals, false) + +} + +// Issue #3447 +func TestRedefineRSSOutputFormat(t *testing.T) { + siteConfig := ` +baseURL = "http://example.com/blog" + +paginate = 1 +defaultContentLanguage = "en" + +disableKinds = ["page", "section", "taxonomy", "taxonomyTerm", "sitemap", "robotsTXT", "404"] + +[outputFormats] +[outputFormats.RSS] +mediatype = "application/rss" +baseName = "feed" + +` + + c := qt.New(t) + + mf := afero.NewMemMapFs() + writeToFs(t, mf, "content/foo.html", `foo`) + + th, h := newTestSitesFromConfig(t, mf, siteConfig) + + err := h.Build(BuildCfg{}) + + c.Assert(err, qt.IsNil) + + th.assertFileContent("public/feed.xml", "Recent content on") + + s := h.Sites[0] + + //Issue #3450 + c.Assert(s.Info.RSSLink, qt.Equals, "http://example.com/blog/feed.xml") + +} + +// Issue #3614 +func TestDotLessOutputFormat(t *testing.T) { + siteConfig := ` +baseURL = "http://example.com/blog" + +paginate = 1 +defaultContentLanguage = "en" + +disableKinds = ["page", "section", "taxonomy", "taxonomyTerm", "sitemap", "robotsTXT", "404"] + +[mediaTypes] +[mediaTypes."text/nodot"] +delimiter = "" +[mediaTypes."text/defaultdelim"] +suffixes = ["defd"] +[mediaTypes."text/nosuffix"] +[mediaTypes."text/customdelim"] +suffixes = ["del"] +delimiter = "_" + +[outputs] +home = [ "DOTLESS", "DEF", "NOS", "CUS" ] + +[outputFormats] +[outputFormats.DOTLESS] +mediatype = "text/nodot" +baseName = "_redirects" # This is how Netlify names their redirect files. +[outputFormats.DEF] +mediatype = "text/defaultdelim" +baseName = "defaultdelimbase" +[outputFormats.NOS] +mediatype = "text/nosuffix" +baseName = "nosuffixbase" +[outputFormats.CUS] +mediatype = "text/customdelim" +baseName = "customdelimbase" + +` + + c := qt.New(t) + + mf := afero.NewMemMapFs() + writeToFs(t, mf, "content/foo.html", `foo`) + writeToFs(t, mf, "layouts/_default/list.dotless", `a dotless`) + writeToFs(t, mf, "layouts/_default/list.def.defd", `default delimim`) + writeToFs(t, mf, "layouts/_default/list.nos", `no suffix`) + writeToFs(t, mf, "layouts/_default/list.cus.del", `custom delim`) + + th, h := newTestSitesFromConfig(t, mf, siteConfig) + + err := h.Build(BuildCfg{}) + + c.Assert(err, qt.IsNil) + + th.assertFileContent("public/_redirects", "a dotless") + th.assertFileContent("public/defaultdelimbase.defd", "default delimim") + // This looks weird, but the user has chosen this definition. + th.assertFileContent("public/nosuffixbase", "no suffix") + th.assertFileContent("public/customdelimbase_del", "custom delim") + + s := h.Sites[0] + home := s.getPage(page.KindHome) + c.Assert(home, qt.Not(qt.IsNil)) + + outputs := home.OutputFormats() + + c.Assert(outputs.Get("DOTLESS").RelPermalink(), qt.Equals, "/blog/_redirects") + c.Assert(outputs.Get("DEF").RelPermalink(), qt.Equals, "/blog/defaultdelimbase.defd") + c.Assert(outputs.Get("NOS").RelPermalink(), qt.Equals, "/blog/nosuffixbase") + c.Assert(outputs.Get("CUS").RelPermalink(), qt.Equals, "/blog/customdelimbase_del") + +} + +func TestCreateSiteOutputFormats(t *testing.T) { + + t.Run("Basic", func(t *testing.T) { + c := qt.New(t) + + outputsConfig := map[string]interface{}{ + page.KindHome: []string{"HTML", "JSON"}, + page.KindSection: []string{"JSON"}, + } + + cfg := viper.New() + cfg.Set("outputs", outputsConfig) + + outputs, err := createSiteOutputFormats(output.DefaultFormats, cfg, false) + c.Assert(err, qt.IsNil) + c.Assert(outputs[page.KindSection], deepEqualsOutputFormats, output.Formats{output.JSONFormat}) + c.Assert(outputs[page.KindHome], deepEqualsOutputFormats, output.Formats{output.HTMLFormat, output.JSONFormat}) + + // Defaults + c.Assert(outputs[page.KindTaxonomy], deepEqualsOutputFormats, output.Formats{output.HTMLFormat, output.RSSFormat}) + c.Assert(outputs[page.KindTaxonomyTerm], deepEqualsOutputFormats, output.Formats{output.HTMLFormat, output.RSSFormat}) + c.Assert(outputs[page.KindPage], deepEqualsOutputFormats, output.Formats{output.HTMLFormat}) + + // These aren't (currently) in use when rendering in Hugo, + // but the pages needs to be assigned an output format, + // so these should also be correct/sensible. + c.Assert(outputs[kindRSS], deepEqualsOutputFormats, output.Formats{output.RSSFormat}) + c.Assert(outputs[kindSitemap], deepEqualsOutputFormats, output.Formats{output.SitemapFormat}) + c.Assert(outputs[kindRobotsTXT], deepEqualsOutputFormats, output.Formats{output.RobotsTxtFormat}) + c.Assert(outputs[kind404], deepEqualsOutputFormats, output.Formats{output.HTMLFormat}) + + }) + + // Issue #4528 + t.Run("Mixed case", func(t *testing.T) { + c := qt.New(t) + cfg := viper.New() + + outputsConfig := map[string]interface{}{ + "taxonomyterm": []string{"JSON"}, + } + cfg.Set("outputs", outputsConfig) + + outputs, err := createSiteOutputFormats(output.DefaultFormats, cfg, false) + c.Assert(err, qt.IsNil) + c.Assert(outputs[page.KindTaxonomyTerm], deepEqualsOutputFormats, output.Formats{output.JSONFormat}) + + }) + +} + +func TestCreateSiteOutputFormatsInvalidConfig(t *testing.T) { + c := qt.New(t) + + outputsConfig := map[string]interface{}{ + page.KindHome: []string{"FOO", "JSON"}, + } + + cfg := viper.New() + cfg.Set("outputs", outputsConfig) + + _, err := createSiteOutputFormats(output.DefaultFormats, cfg, false) + c.Assert(err, qt.Not(qt.IsNil)) +} + +func TestCreateSiteOutputFormatsEmptyConfig(t *testing.T) { + c := qt.New(t) + + outputsConfig := map[string]interface{}{ + page.KindHome: []string{}, + } + + cfg := viper.New() + cfg.Set("outputs", outputsConfig) + + outputs, err := createSiteOutputFormats(output.DefaultFormats, cfg, false) + c.Assert(err, qt.IsNil) + c.Assert(outputs[page.KindHome], deepEqualsOutputFormats, output.Formats{output.HTMLFormat, output.RSSFormat}) +} + +func TestCreateSiteOutputFormatsCustomFormats(t *testing.T) { + c := qt.New(t) + + outputsConfig := map[string]interface{}{ + page.KindHome: []string{}, + } + + cfg := viper.New() + cfg.Set("outputs", outputsConfig) + + var ( + customRSS = output.Format{Name: "RSS", BaseName: "customRSS"} + customHTML = output.Format{Name: "HTML", BaseName: "customHTML"} + ) + + outputs, err := createSiteOutputFormats(output.Formats{customRSS, customHTML}, cfg, false) + c.Assert(err, qt.IsNil) + c.Assert(outputs[page.KindHome], deepEqualsOutputFormats, output.Formats{customHTML, customRSS}) +} + +// https://github.com/gohugoio/hugo/issues/5849 +func TestOutputFormatPermalinkable(t *testing.T) { + + config := ` +baseURL = "https://example.com" + + + +# DAMP is similar to AMP, but not permalinkable. +[outputFormats] +[outputFormats.damp] +mediaType = "text/html" +path = "damp" +[outputFormats.ramp] +mediaType = "text/html" +path = "ramp" +permalinkable = true +[outputFormats.base] +mediaType = "text/html" +isHTML = true +baseName = "that" +permalinkable = true +[outputFormats.nobase] +mediaType = "application/json" +permalinkable = true + +` + + b := newTestSitesBuilder(t).WithConfigFile("toml", config) + b.WithContent("_index.md", ` +--- +Title: Home Sweet Home +outputs: [ "html", "amp", "damp", "base" ] +--- + +`) + + b.WithContent("blog/html-amp.md", ` +--- +Title: AMP and HTML +outputs: [ "html", "amp" ] +--- + +`) + + b.WithContent("blog/html-damp.md", ` +--- +Title: DAMP and HTML +outputs: [ "html", "damp" ] +--- + +`) + + b.WithContent("blog/html-ramp.md", ` +--- +Title: RAMP and HTML +outputs: [ "html", "ramp" ] +--- + +`) + + b.WithContent("blog/html.md", ` +--- +Title: HTML only +outputs: [ "html" ] +--- + +`) + + b.WithContent("blog/amp.md", ` +--- +Title: AMP only +outputs: [ "amp" ] +--- + +`) + + b.WithContent("blog/html-base-nobase.md", ` +--- +Title: HTML, Base and Nobase +outputs: [ "html", "base", "nobase" ] +--- + +`) + + const commonTemplate = ` +This RelPermalink: {{ .RelPermalink }} +Output Formats: {{ len .OutputFormats }};{{ range .OutputFormats }}{{ .Name }};{{ .RelPermalink }}|{{ end }} + +` + + b.WithTemplatesAdded("index.html", commonTemplate) + b.WithTemplatesAdded("_default/single.html", commonTemplate) + b.WithTemplatesAdded("_default/single.json", commonTemplate) + + b.Build(BuildCfg{}) + + b.AssertFileContent("public/index.html", + "This RelPermalink: /", + "Output Formats: 4;HTML;/|AMP;/amp/|damp;/damp/|base;/that.html|", + ) + + b.AssertFileContent("public/amp/index.html", + "This RelPermalink: /amp/", + "Output Formats: 4;HTML;/|AMP;/amp/|damp;/damp/|base;/that.html|", + ) + + b.AssertFileContent("public/blog/html-amp/index.html", + "Output Formats: 2;HTML;/blog/html-amp/|AMP;/amp/blog/html-amp/|", + "This RelPermalink: /blog/html-amp/") + + b.AssertFileContent("public/amp/blog/html-amp/index.html", + "Output Formats: 2;HTML;/blog/html-amp/|AMP;/amp/blog/html-amp/|", + "This RelPermalink: /amp/blog/html-amp/") + + // Damp is not permalinkable + b.AssertFileContent("public/damp/blog/html-damp/index.html", + "This RelPermalink: /blog/html-damp/", + "Output Formats: 2;HTML;/blog/html-damp/|damp;/damp/blog/html-damp/|") + + b.AssertFileContent("public/blog/html-ramp/index.html", + "This RelPermalink: /blog/html-ramp/", + "Output Formats: 2;HTML;/blog/html-ramp/|ramp;/ramp/blog/html-ramp/|") + + b.AssertFileContent("public/ramp/blog/html-ramp/index.html", + "This RelPermalink: /ramp/blog/html-ramp/", + "Output Formats: 2;HTML;/blog/html-ramp/|ramp;/ramp/blog/html-ramp/|") + + // https://github.com/gohugoio/hugo/issues/5877 + outputFormats := "Output Formats: 3;HTML;/blog/html-base-nobase/|base;/blog/html-base-nobase/that.html|nobase;/blog/html-base-nobase/index.json|" + + b.AssertFileContent("public/blog/html-base-nobase/index.json", + "This RelPermalink: /blog/html-base-nobase/index.json", + outputFormats, + ) + + b.AssertFileContent("public/blog/html-base-nobase/that.html", + "This RelPermalink: /blog/html-base-nobase/that.html", + outputFormats, + ) + + b.AssertFileContent("public/blog/html-base-nobase/index.html", + "This RelPermalink: /blog/html-base-nobase/", + outputFormats, + ) + +} + +func TestSiteWithPageNoOutputs(t *testing.T) { + t.Parallel() + + b := newTestSitesBuilder(t) + b.WithConfigFile("toml", ` +baseURL = "https://example.com" + +[outputFormats.o1] +mediaType = "text/html" + + + +`) + b.WithContent("outputs-empty.md", `--- +title: "Empty Outputs" +outputs: [] +--- + +Word1. Word2. + +`, + "outputs-string.md", `--- +title: "Outputs String" +outputs: "o1" +--- + +Word1. Word2. + +`) + + b.WithTemplates("index.html", ` +{{ range .Site.RegularPages }} +WordCount: {{ .WordCount }} +{{ end }} +`) + + b.WithTemplates("_default/single.html", `HTML: {{ .Content }}`) + b.WithTemplates("_default/single.o1.html", `O1: {{ .Content }}`) + + b.Build(BuildCfg{}) + + b.AssertFileContent( + "public/index.html", + " WordCount: 2") + + b.AssertFileContent("public/outputs-empty/index.html", "HTML:", "Word1. Word2.") + b.AssertFileContent("public/outputs-string/index.html", "O1:", "Word1. Word2.") + +} |