github.com/graemephi/kahugo@v0.62.3-0.20211121071557-d78c0423784d/hugolib/site_output_test.go (about) 1 // Copyright 2019 The Hugo Authors. All rights reserved. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // http://www.apache.org/licenses/LICENSE-2.0 7 // 8 // Unless required by applicable law or agreed to in writing, software 9 // distributed under the License is distributed on an "AS IS" BASIS, 10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 // See the License for the specific language governing permissions and 12 // limitations under the License. 13 14 package hugolib 15 16 import ( 17 "fmt" 18 "strings" 19 "testing" 20 21 qt "github.com/frankban/quicktest" 22 "github.com/gohugoio/hugo/config" 23 "github.com/gohugoio/hugo/resources/page" 24 25 "github.com/spf13/afero" 26 27 "github.com/gohugoio/hugo/helpers" 28 "github.com/gohugoio/hugo/output" 29 ) 30 31 func TestSiteWithPageOutputs(t *testing.T) { 32 for _, outputs := range [][]string{{"html", "json", "calendar"}, {"json"}} { 33 outputs := outputs 34 t.Run(fmt.Sprintf("%v", outputs), func(t *testing.T) { 35 t.Parallel() 36 doTestSiteWithPageOutputs(t, outputs) 37 }) 38 } 39 } 40 41 func doTestSiteWithPageOutputs(t *testing.T, outputs []string) { 42 outputsStr := strings.Replace(fmt.Sprintf("%q", outputs), " ", ", ", -1) 43 44 siteConfig := ` 45 baseURL = "http://example.com/blog" 46 47 paginate = 1 48 defaultContentLanguage = "en" 49 50 disableKinds = ["section", "term", "taxonomy", "RSS", "sitemap", "robotsTXT", "404"] 51 52 [Taxonomies] 53 tag = "tags" 54 category = "categories" 55 56 defaultContentLanguage = "en" 57 58 59 [languages] 60 61 [languages.en] 62 title = "Title in English" 63 languageName = "English" 64 weight = 1 65 66 [languages.nn] 67 languageName = "Nynorsk" 68 weight = 2 69 title = "Tittel på Nynorsk" 70 71 ` 72 73 pageTemplate := `--- 74 title: "%s" 75 outputs: %s 76 --- 77 # Doc 78 79 {{< myShort >}} 80 81 {{< myOtherShort >}} 82 83 ` 84 85 b := newTestSitesBuilder(t).WithConfigFile("toml", siteConfig) 86 b.WithI18n("en.toml", ` 87 [elbow] 88 other = "Elbow" 89 `, "nn.toml", ` 90 [elbow] 91 other = "Olboge" 92 `) 93 94 b.WithTemplates( 95 // Case issue partials #3333 96 "layouts/partials/GoHugo.html", `Go Hugo Partial`, 97 "layouts/_default/baseof.json", `START JSON:{{block "main" .}}default content{{ end }}:END JSON`, 98 "layouts/_default/baseof.html", `START HTML:{{block "main" .}}default content{{ end }}:END HTML`, 99 "layouts/shortcodes/myOtherShort.html", `OtherShort: {{ "<h1>Hi!</h1>" | safeHTML }}`, 100 "layouts/shortcodes/myShort.html", `ShortHTML`, 101 "layouts/shortcodes/myShort.json", `ShortJSON`, 102 103 "layouts/_default/list.json", `{{ define "main" }} 104 List JSON|{{ .Title }}|{{ .Content }}|Alt formats: {{ len .AlternativeOutputFormats -}}| 105 {{- range .AlternativeOutputFormats -}} 106 Alt Output: {{ .Name -}}| 107 {{- end -}}| 108 {{- range .OutputFormats -}} 109 Output/Rel: {{ .Name -}}/{{ .Rel }}|{{ .MediaType }} 110 {{- end -}} 111 {{ with .OutputFormats.Get "JSON" }} 112 <atom:link href={{ .Permalink }} rel="self" type="{{ .MediaType }}" /> 113 {{ end }} 114 {{ .Site.Language.Lang }}: {{ T "elbow" -}} 115 {{ end }} 116 `, 117 "layouts/_default/list.html", `{{ define "main" }} 118 List HTML|{{.Title }}| 119 {{- with .OutputFormats.Get "HTML" -}} 120 <atom:link href={{ .Permalink }} rel="self" type="{{ .MediaType }}" /> 121 {{- end -}} 122 {{ .Site.Language.Lang }}: {{ T "elbow" -}} 123 Partial Hugo 1: {{ partial "GoHugo.html" . }} 124 Partial Hugo 2: {{ partial "GoHugo" . -}} 125 Content: {{ .Content }} 126 Len Pages: {{ .Kind }} {{ len .Site.RegularPages }} Page Number: {{ .Paginator.PageNumber }} 127 {{ end }} 128 `, 129 "layouts/_default/single.html", `{{ define "main" }}{{ .Content }}{{ end }}`, 130 ) 131 132 b.WithContent("_index.md", fmt.Sprintf(pageTemplate, "JSON Home", outputsStr)) 133 b.WithContent("_index.nn.md", fmt.Sprintf(pageTemplate, "JSON Nynorsk Heim", outputsStr)) 134 135 for i := 1; i <= 10; i++ { 136 b.WithContent(fmt.Sprintf("p%d.md", i), fmt.Sprintf(pageTemplate, fmt.Sprintf("Page %d", i), outputsStr)) 137 } 138 139 b.Build(BuildCfg{}) 140 141 s := b.H.Sites[0] 142 b.Assert(s.language.Lang, qt.Equals, "en") 143 144 home := s.getPage(page.KindHome) 145 146 b.Assert(home, qt.Not(qt.IsNil)) 147 148 lenOut := len(outputs) 149 150 b.Assert(len(home.OutputFormats()), qt.Equals, lenOut) 151 152 // There is currently always a JSON output to make it simpler ... 153 altFormats := lenOut - 1 154 hasHTML := helpers.InStringArray(outputs, "html") 155 b.AssertFileContent("public/index.json", 156 "List JSON", 157 fmt.Sprintf("Alt formats: %d", altFormats), 158 ) 159 160 if hasHTML { 161 b.AssertFileContent("public/index.json", 162 "Alt Output: HTML", 163 "Output/Rel: JSON/alternate|", 164 "Output/Rel: HTML/canonical|", 165 "en: Elbow", 166 "ShortJSON", 167 "OtherShort: <h1>Hi!</h1>", 168 ) 169 170 b.AssertFileContent("public/index.html", 171 // The HTML entity is a deliberate part of this test: The HTML templates are 172 // parsed with html/template. 173 `List HTML|JSON Home|<atom:link href=http://example.com/blog/ rel="self" type="text/html" />`, 174 "en: Elbow", 175 "ShortHTML", 176 "OtherShort: <h1>Hi!</h1>", 177 "Len Pages: home 10", 178 ) 179 b.AssertFileContent("public/page/2/index.html", "Page Number: 2") 180 b.Assert(b.CheckExists("public/page/2/index.json"), qt.Equals, false) 181 182 b.AssertFileContent("public/nn/index.html", 183 "List HTML|JSON Nynorsk Heim|", 184 "nn: Olboge") 185 } else { 186 b.AssertFileContent("public/index.json", 187 "Output/Rel: JSON/canonical|", 188 // JSON is plain text, so no need to safeHTML this and that 189 `<atom:link href=http://example.com/blog/index.json rel="self" type="application/json" />`, 190 "ShortJSON", 191 "OtherShort: <h1>Hi!</h1>", 192 ) 193 b.AssertFileContent("public/nn/index.json", 194 "List JSON|JSON Nynorsk Heim|", 195 "nn: Olboge", 196 "ShortJSON", 197 ) 198 } 199 200 of := home.OutputFormats() 201 202 json := of.Get("JSON") 203 b.Assert(json, qt.Not(qt.IsNil)) 204 b.Assert(json.RelPermalink(), qt.Equals, "/blog/index.json") 205 b.Assert(json.Permalink(), qt.Equals, "http://example.com/blog/index.json") 206 207 if helpers.InStringArray(outputs, "cal") { 208 cal := of.Get("calendar") 209 b.Assert(cal, qt.Not(qt.IsNil)) 210 b.Assert(cal.RelPermalink(), qt.Equals, "/blog/index.ics") 211 b.Assert(cal.Permalink(), qt.Equals, "webcal://example.com/blog/index.ics") 212 } 213 214 b.Assert(home.HasShortcode("myShort"), qt.Equals, true) 215 b.Assert(home.HasShortcode("doesNotExist"), qt.Equals, false) 216 } 217 218 // Issue #3447 219 func TestRedefineRSSOutputFormat(t *testing.T) { 220 siteConfig := ` 221 baseURL = "http://example.com/blog" 222 223 paginate = 1 224 defaultContentLanguage = "en" 225 226 disableKinds = ["page", "section", "term", "taxonomy", "sitemap", "robotsTXT", "404"] 227 228 [outputFormats] 229 [outputFormats.RSS] 230 mediatype = "application/rss" 231 baseName = "feed" 232 233 ` 234 235 c := qt.New(t) 236 237 mf := afero.NewMemMapFs() 238 writeToFs(t, mf, "content/foo.html", `foo`) 239 240 th, h := newTestSitesFromConfig(t, mf, siteConfig) 241 242 err := h.Build(BuildCfg{}) 243 244 c.Assert(err, qt.IsNil) 245 246 th.assertFileContent("public/feed.xml", "Recent content on") 247 248 s := h.Sites[0] 249 250 // Issue #3450 251 c.Assert(s.Info.RSSLink, qt.Equals, "http://example.com/blog/feed.xml") 252 } 253 254 // Issue #3614 255 func TestDotLessOutputFormat(t *testing.T) { 256 siteConfig := ` 257 baseURL = "http://example.com/blog" 258 259 paginate = 1 260 defaultContentLanguage = "en" 261 262 disableKinds = ["page", "section", "term", "taxonomy", "sitemap", "robotsTXT", "404"] 263 264 [mediaTypes] 265 [mediaTypes."text/nodot"] 266 delimiter = "" 267 [mediaTypes."text/defaultdelim"] 268 suffixes = ["defd"] 269 [mediaTypes."text/nosuffix"] 270 [mediaTypes."text/customdelim"] 271 suffixes = ["del"] 272 delimiter = "_" 273 274 [outputs] 275 home = [ "DOTLESS", "DEF", "NOS", "CUS" ] 276 277 [outputFormats] 278 [outputFormats.DOTLESS] 279 mediatype = "text/nodot" 280 baseName = "_redirects" # This is how Netlify names their redirect files. 281 [outputFormats.DEF] 282 mediatype = "text/defaultdelim" 283 baseName = "defaultdelimbase" 284 [outputFormats.NOS] 285 mediatype = "text/nosuffix" 286 baseName = "nosuffixbase" 287 [outputFormats.CUS] 288 mediatype = "text/customdelim" 289 baseName = "customdelimbase" 290 291 ` 292 293 c := qt.New(t) 294 295 mf := afero.NewMemMapFs() 296 writeToFs(t, mf, "content/foo.html", `foo`) 297 writeToFs(t, mf, "layouts/_default/list.dotless", `a dotless`) 298 writeToFs(t, mf, "layouts/_default/list.def.defd", `default delimim`) 299 writeToFs(t, mf, "layouts/_default/list.nos", `no suffix`) 300 writeToFs(t, mf, "layouts/_default/list.cus.del", `custom delim`) 301 302 th, h := newTestSitesFromConfig(t, mf, siteConfig) 303 304 err := h.Build(BuildCfg{}) 305 306 c.Assert(err, qt.IsNil) 307 308 s := h.Sites[0] 309 310 th.assertFileContent("public/_redirects", "a dotless") 311 th.assertFileContent("public/defaultdelimbase.defd", "default delimim") 312 // This looks weird, but the user has chosen this definition. 313 th.assertFileContent("public/nosuffixbase", "no suffix") 314 th.assertFileContent("public/customdelimbase_del", "custom delim") 315 316 home := s.getPage(page.KindHome) 317 c.Assert(home, qt.Not(qt.IsNil)) 318 319 outputs := home.OutputFormats() 320 321 c.Assert(outputs.Get("DOTLESS").RelPermalink(), qt.Equals, "/blog/_redirects") 322 c.Assert(outputs.Get("DEF").RelPermalink(), qt.Equals, "/blog/defaultdelimbase.defd") 323 c.Assert(outputs.Get("NOS").RelPermalink(), qt.Equals, "/blog/nosuffixbase") 324 c.Assert(outputs.Get("CUS").RelPermalink(), qt.Equals, "/blog/customdelimbase_del") 325 } 326 327 func TestCreateSiteOutputFormats(t *testing.T) { 328 t.Run("Basic", func(t *testing.T) { 329 c := qt.New(t) 330 331 outputsConfig := map[string]interface{}{ 332 page.KindHome: []string{"HTML", "JSON"}, 333 page.KindSection: []string{"JSON"}, 334 } 335 336 cfg := config.New() 337 cfg.Set("outputs", outputsConfig) 338 339 outputs, err := createSiteOutputFormats(output.DefaultFormats, cfg.GetStringMap("outputs"), false) 340 c.Assert(err, qt.IsNil) 341 c.Assert(outputs[page.KindSection], deepEqualsOutputFormats, output.Formats{output.JSONFormat}) 342 c.Assert(outputs[page.KindHome], deepEqualsOutputFormats, output.Formats{output.HTMLFormat, output.JSONFormat}) 343 344 // Defaults 345 c.Assert(outputs[page.KindTerm], deepEqualsOutputFormats, output.Formats{output.HTMLFormat, output.RSSFormat}) 346 c.Assert(outputs[page.KindTaxonomy], deepEqualsOutputFormats, output.Formats{output.HTMLFormat, output.RSSFormat}) 347 c.Assert(outputs[page.KindPage], deepEqualsOutputFormats, output.Formats{output.HTMLFormat}) 348 349 // These aren't (currently) in use when rendering in Hugo, 350 // but the pages needs to be assigned an output format, 351 // so these should also be correct/sensible. 352 c.Assert(outputs[kindRSS], deepEqualsOutputFormats, output.Formats{output.RSSFormat}) 353 c.Assert(outputs[kindSitemap], deepEqualsOutputFormats, output.Formats{output.SitemapFormat}) 354 c.Assert(outputs[kindRobotsTXT], deepEqualsOutputFormats, output.Formats{output.RobotsTxtFormat}) 355 c.Assert(outputs[kind404], deepEqualsOutputFormats, output.Formats{output.HTMLFormat}) 356 }) 357 358 // Issue #4528 359 t.Run("Mixed case", func(t *testing.T) { 360 c := qt.New(t) 361 cfg := config.New() 362 363 outputsConfig := map[string]interface{}{ 364 // Note that we in Hugo 0.53.0 renamed this Kind to "taxonomy", 365 // but keep this test to test the legacy mapping. 366 "taxonomyterm": []string{"JSON"}, 367 } 368 cfg.Set("outputs", outputsConfig) 369 370 outputs, err := createSiteOutputFormats(output.DefaultFormats, cfg.GetStringMap("outputs"), false) 371 c.Assert(err, qt.IsNil) 372 c.Assert(outputs[page.KindTaxonomy], deepEqualsOutputFormats, output.Formats{output.JSONFormat}) 373 }) 374 } 375 376 func TestCreateSiteOutputFormatsInvalidConfig(t *testing.T) { 377 c := qt.New(t) 378 379 outputsConfig := map[string]interface{}{ 380 page.KindHome: []string{"FOO", "JSON"}, 381 } 382 383 cfg := config.New() 384 cfg.Set("outputs", outputsConfig) 385 386 _, err := createSiteOutputFormats(output.DefaultFormats, cfg.GetStringMap("outputs"), false) 387 c.Assert(err, qt.Not(qt.IsNil)) 388 } 389 390 func TestCreateSiteOutputFormatsEmptyConfig(t *testing.T) { 391 c := qt.New(t) 392 393 outputsConfig := map[string]interface{}{ 394 page.KindHome: []string{}, 395 } 396 397 cfg := config.New() 398 cfg.Set("outputs", outputsConfig) 399 400 outputs, err := createSiteOutputFormats(output.DefaultFormats, cfg.GetStringMap("outputs"), false) 401 c.Assert(err, qt.IsNil) 402 c.Assert(outputs[page.KindHome], deepEqualsOutputFormats, output.Formats{output.HTMLFormat, output.RSSFormat}) 403 } 404 405 func TestCreateSiteOutputFormatsCustomFormats(t *testing.T) { 406 c := qt.New(t) 407 408 outputsConfig := map[string]interface{}{ 409 page.KindHome: []string{}, 410 } 411 412 cfg := config.New() 413 cfg.Set("outputs", outputsConfig) 414 415 var ( 416 customRSS = output.Format{Name: "RSS", BaseName: "customRSS"} 417 customHTML = output.Format{Name: "HTML", BaseName: "customHTML"} 418 ) 419 420 outputs, err := createSiteOutputFormats(output.Formats{customRSS, customHTML}, cfg.GetStringMap("outputs"), false) 421 c.Assert(err, qt.IsNil) 422 c.Assert(outputs[page.KindHome], deepEqualsOutputFormats, output.Formats{customHTML, customRSS}) 423 } 424 425 // https://github.com/gohugoio/hugo/issues/5849 426 func TestOutputFormatPermalinkable(t *testing.T) { 427 config := ` 428 baseURL = "https://example.com" 429 430 431 432 # DAMP is similar to AMP, but not permalinkable. 433 [outputFormats] 434 [outputFormats.damp] 435 mediaType = "text/html" 436 path = "damp" 437 [outputFormats.ramp] 438 mediaType = "text/html" 439 path = "ramp" 440 permalinkable = true 441 [outputFormats.base] 442 mediaType = "text/html" 443 isHTML = true 444 baseName = "that" 445 permalinkable = true 446 [outputFormats.nobase] 447 mediaType = "application/json" 448 permalinkable = true 449 450 ` 451 452 b := newTestSitesBuilder(t).WithConfigFile("toml", config) 453 b.WithContent("_index.md", ` 454 --- 455 Title: Home Sweet Home 456 outputs: [ "html", "amp", "damp", "base" ] 457 --- 458 459 `) 460 461 b.WithContent("blog/html-amp.md", ` 462 --- 463 Title: AMP and HTML 464 outputs: [ "html", "amp" ] 465 --- 466 467 `) 468 469 b.WithContent("blog/html-damp.md", ` 470 --- 471 Title: DAMP and HTML 472 outputs: [ "html", "damp" ] 473 --- 474 475 `) 476 477 b.WithContent("blog/html-ramp.md", ` 478 --- 479 Title: RAMP and HTML 480 outputs: [ "html", "ramp" ] 481 --- 482 483 `) 484 485 b.WithContent("blog/html.md", ` 486 --- 487 Title: HTML only 488 outputs: [ "html" ] 489 --- 490 491 `) 492 493 b.WithContent("blog/amp.md", ` 494 --- 495 Title: AMP only 496 outputs: [ "amp" ] 497 --- 498 499 `) 500 501 b.WithContent("blog/html-base-nobase.md", ` 502 --- 503 Title: HTML, Base and Nobase 504 outputs: [ "html", "base", "nobase" ] 505 --- 506 507 `) 508 509 const commonTemplate = ` 510 This RelPermalink: {{ .RelPermalink }} 511 Output Formats: {{ len .OutputFormats }};{{ range .OutputFormats }}{{ .Name }};{{ .RelPermalink }}|{{ end }} 512 513 ` 514 515 b.WithTemplatesAdded("index.html", commonTemplate) 516 b.WithTemplatesAdded("_default/single.html", commonTemplate) 517 b.WithTemplatesAdded("_default/single.json", commonTemplate) 518 519 b.Build(BuildCfg{}) 520 521 b.AssertFileContent("public/index.html", 522 "This RelPermalink: /", 523 "Output Formats: 4;HTML;/|AMP;/amp/|damp;/damp/|base;/that.html|", 524 ) 525 526 b.AssertFileContent("public/amp/index.html", 527 "This RelPermalink: /amp/", 528 "Output Formats: 4;HTML;/|AMP;/amp/|damp;/damp/|base;/that.html|", 529 ) 530 531 b.AssertFileContent("public/blog/html-amp/index.html", 532 "Output Formats: 2;HTML;/blog/html-amp/|AMP;/amp/blog/html-amp/|", 533 "This RelPermalink: /blog/html-amp/") 534 535 b.AssertFileContent("public/amp/blog/html-amp/index.html", 536 "Output Formats: 2;HTML;/blog/html-amp/|AMP;/amp/blog/html-amp/|", 537 "This RelPermalink: /amp/blog/html-amp/") 538 539 // Damp is not permalinkable 540 b.AssertFileContent("public/damp/blog/html-damp/index.html", 541 "This RelPermalink: /blog/html-damp/", 542 "Output Formats: 2;HTML;/blog/html-damp/|damp;/damp/blog/html-damp/|") 543 544 b.AssertFileContent("public/blog/html-ramp/index.html", 545 "This RelPermalink: /blog/html-ramp/", 546 "Output Formats: 2;HTML;/blog/html-ramp/|ramp;/ramp/blog/html-ramp/|") 547 548 b.AssertFileContent("public/ramp/blog/html-ramp/index.html", 549 "This RelPermalink: /ramp/blog/html-ramp/", 550 "Output Formats: 2;HTML;/blog/html-ramp/|ramp;/ramp/blog/html-ramp/|") 551 552 // https://github.com/gohugoio/hugo/issues/5877 553 outputFormats := "Output Formats: 3;HTML;/blog/html-base-nobase/|base;/blog/html-base-nobase/that.html|nobase;/blog/html-base-nobase/index.json|" 554 555 b.AssertFileContent("public/blog/html-base-nobase/index.json", 556 "This RelPermalink: /blog/html-base-nobase/index.json", 557 outputFormats, 558 ) 559 560 b.AssertFileContent("public/blog/html-base-nobase/that.html", 561 "This RelPermalink: /blog/html-base-nobase/that.html", 562 outputFormats, 563 ) 564 565 b.AssertFileContent("public/blog/html-base-nobase/index.html", 566 "This RelPermalink: /blog/html-base-nobase/", 567 outputFormats, 568 ) 569 } 570 571 func TestSiteWithPageNoOutputs(t *testing.T) { 572 t.Parallel() 573 574 b := newTestSitesBuilder(t) 575 b.WithConfigFile("toml", ` 576 baseURL = "https://example.com" 577 578 [outputFormats.o1] 579 mediaType = "text/html" 580 581 582 583 `) 584 b.WithContent("outputs-empty.md", `--- 585 title: "Empty Outputs" 586 outputs: [] 587 --- 588 589 Word1. Word2. 590 591 `, 592 "outputs-string.md", `--- 593 title: "Outputs String" 594 outputs: "o1" 595 --- 596 597 Word1. Word2. 598 599 `) 600 601 b.WithTemplates("index.html", ` 602 {{ range .Site.RegularPages }} 603 WordCount: {{ .WordCount }} 604 {{ end }} 605 `) 606 607 b.WithTemplates("_default/single.html", `HTML: {{ .Content }}`) 608 b.WithTemplates("_default/single.o1.html", `O1: {{ .Content }}`) 609 610 b.Build(BuildCfg{}) 611 612 b.AssertFileContent( 613 "public/index.html", 614 " WordCount: 2") 615 616 b.AssertFileContent("public/outputs-empty/index.html", "HTML:", "Word1. Word2.") 617 b.AssertFileContent("public/outputs-string/index.html", "O1:", "Word1. Word2.") 618 }