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 14package hugolib 15 16import ( 17 "fmt" 18 "html/template" 19 "os" 20 "path/filepath" 21 "strings" 22 "testing" 23 "time" 24 25 "github.com/gohugoio/hugo/htesting" 26 27 "github.com/gohugoio/hugo/markup/asciidocext" 28 29 "github.com/gohugoio/hugo/config" 30 31 "github.com/gohugoio/hugo/common/loggers" 32 33 "github.com/gohugoio/hugo/hugofs" 34 35 "github.com/gohugoio/hugo/resources/page" 36 "github.com/gohugoio/hugo/resources/resource" 37 "github.com/spf13/afero" 38 39 qt "github.com/frankban/quicktest" 40 "github.com/gohugoio/hugo/deps" 41 "github.com/gohugoio/hugo/helpers" 42) 43 44const ( 45 homePage = "---\ntitle: Home\n---\nHome Page Content\n" 46 simplePage = "---\ntitle: Simple\n---\nSimple Page\n" 47 48 simplePageRFC3339Date = "---\ntitle: RFC3339 Date\ndate: \"2013-05-17T16:59:30Z\"\n---\nrfc3339 content" 49 50 simplePageWithoutSummaryDelimiter = `--- 51title: SimpleWithoutSummaryDelimiter 52--- 53[Lorem ipsum](https://lipsum.com/) dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. 54 55Additional text. 56 57Further text. 58` 59 60 simplePageWithSummaryDelimiter = `--- 61title: Simple 62--- 63Summary Next Line 64 65<!--more--> 66Some more text 67` 68 69 simplePageWithSummaryParameter = `--- 70title: SimpleWithSummaryParameter 71summary: "Page with summary parameter and [a link](http://www.example.com/)" 72--- 73 74Some text. 75 76Some more text. 77` 78 79 simplePageWithSummaryDelimiterAndMarkdownThatCrossesBorder = `--- 80title: Simple 81--- 82The [best static site generator][hugo].[^1] 83<!--more--> 84[hugo]: http://gohugo.io/ 85[^1]: Many people say so. 86` 87 simplePageWithShortcodeInSummary = `--- 88title: Simple 89--- 90Summary Next Line. {{<figure src="/not/real" >}}. 91More text here. 92 93Some more text 94` 95 96 simplePageWithSummaryDelimiterSameLine = `--- 97title: Simple 98--- 99Summary Same Line<!--more--> 100 101Some more text 102` 103 104 simplePageWithAllCJKRunes = `--- 105title: Simple 106--- 107 108 109€ € € € € 110你好 111도형이 112カテゴリー 113 114 115` 116 117 simplePageWithMainEnglishWithCJKRunes = `--- 118title: Simple 119--- 120 121 122In Chinese, 好 means good. In Chinese, 好 means good. 123In Chinese, 好 means good. In Chinese, 好 means good. 124In Chinese, 好 means good. In Chinese, 好 means good. 125In Chinese, 好 means good. In Chinese, 好 means good. 126In Chinese, 好 means good. In Chinese, 好 means good. 127In Chinese, 好 means good. In Chinese, 好 means good. 128In Chinese, 好 means good. In Chinese, 好 means good. 129More then 70 words. 130 131 132` 133 simplePageWithMainEnglishWithCJKRunesSummary = "In Chinese, 好 means good. In Chinese, 好 means good. " + 134 "In Chinese, 好 means good. In Chinese, 好 means good. " + 135 "In Chinese, 好 means good. In Chinese, 好 means good. " + 136 "In Chinese, 好 means good. In Chinese, 好 means good. " + 137 "In Chinese, 好 means good. In Chinese, 好 means good. " + 138 "In Chinese, 好 means good. In Chinese, 好 means good. " + 139 "In Chinese, 好 means good. In Chinese, 好 means good." 140 141 simplePageWithIsCJKLanguageFalse = `--- 142title: Simple 143isCJKLanguage: false 144--- 145 146In Chinese, 好的啊 means good. In Chinese, 好的呀 means good. 147In Chinese, 好的啊 means good. In Chinese, 好的呀 means good. 148In Chinese, 好的啊 means good. In Chinese, 好的呀 means good. 149In Chinese, 好的啊 means good. In Chinese, 好的呀 means good. 150In Chinese, 好的啊 means good. In Chinese, 好的呀 means good. 151In Chinese, 好的啊 means good. In Chinese, 好的呀 means good. 152In Chinese, 好的啊 means good. In Chinese, 好的呀呀 means good enough. 153More then 70 words. 154 155 156` 157 simplePageWithIsCJKLanguageFalseSummary = "In Chinese, 好的啊 means good. In Chinese, 好的呀 means good. " + 158 "In Chinese, 好的啊 means good. In Chinese, 好的呀 means good. " + 159 "In Chinese, 好的啊 means good. In Chinese, 好的呀 means good. " + 160 "In Chinese, 好的啊 means good. In Chinese, 好的呀 means good. " + 161 "In Chinese, 好的啊 means good. In Chinese, 好的呀 means good. " + 162 "In Chinese, 好的啊 means good. In Chinese, 好的呀 means good. " + 163 "In Chinese, 好的啊 means good. In Chinese, 好的呀呀 means good enough." 164 165 simplePageWithLongContent = `--- 166title: Simple 167--- 168 169Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor 170incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis 171nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. 172Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu 173fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in 174culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit 175amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore 176et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation 177ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor 178in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla 179pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui 180officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, 181consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et 182dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco 183laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in 184reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. 185Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia 186deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur 187adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna 188aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi 189ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in 190voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint 191occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim 192id est laborum. Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed 193do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim 194veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo 195consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse 196cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non 197proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem 198ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor 199incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis 200nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. 201Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu 202fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in 203culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit 204amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore 205et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation 206ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor 207in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla 208pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui 209officia deserunt mollit anim id est laborum.` 210 211 pageWithToC = `--- 212title: TOC 213--- 214For some moments the old man did not reply. He stood with bowed head, buried in deep thought. But at last he spoke. 215 216## AA 217 218I have no idea, of course, how long it took me to reach the limit of the plain, 219but at last I entered the foothills, following a pretty little canyon upward 220toward the mountains. Beside me frolicked a laughing brooklet, hurrying upon 221its noisy way down to the silent sea. In its quieter pools I discovered many 222small fish, of four-or five-pound weight I should imagine. In appearance, 223except as to size and color, they were not unlike the whale of our own seas. As 224I watched them playing about I discovered, not only that they suckled their 225young, but that at intervals they rose to the surface to breathe as well as to 226feed upon certain grasses and a strange, scarlet lichen which grew upon the 227rocks just above the water line. 228 229### AAA 230 231I remember I felt an extraordinary persuasion that I was being played with, 232that presently, when I was upon the very verge of safety, this mysterious 233death--as swift as the passage of light--would leap after me from the pit about 234the cylinder and strike me down. ## BB 235 236### BBB 237 238"You're a great Granser," he cried delightedly, "always making believe them little marks mean something." 239` 240 241 simplePageWithAdditionalExtension = `+++ 242[blackfriday] 243 extensions = ["hardLineBreak"] 244+++ 245first line. 246second line. 247 248fourth line. 249` 250 251 simplePageWithURL = `--- 252title: Simple 253url: simple/url/ 254--- 255Simple Page With URL` 256 257 simplePageWithSlug = `--- 258title: Simple 259slug: simple-slug 260--- 261Simple Page With Slug` 262 263 simplePageWithDate = `--- 264title: Simple 265date: '2013-10-15T06:16:13' 266--- 267Simple Page With Date` 268 269 UTF8Page = `--- 270title: ラーメン 271--- 272UTF8 Page` 273 274 UTF8PageWithURL = `--- 275title: ラーメン 276url: ラーメン/url/ 277--- 278UTF8 Page With URL` 279 280 UTF8PageWithSlug = `--- 281title: ラーメン 282slug: ラーメン-slug 283--- 284UTF8 Page With Slug` 285 286 UTF8PageWithDate = `--- 287title: ラーメン 288date: '2013-10-15T06:16:13' 289--- 290UTF8 Page With Date` 291) 292 293func checkPageTitle(t *testing.T, page page.Page, title string) { 294 if page.Title() != title { 295 t.Fatalf("Page title is: %s. Expected %s", page.Title(), title) 296 } 297} 298 299func checkPageContent(t *testing.T, page page.Page, expected string, msg ...interface{}) { 300 t.Helper() 301 a := normalizeContent(expected) 302 b := normalizeContent(content(page)) 303 if a != b { 304 t.Fatalf("Page content is:\n%q\nExpected:\n%q (%q)", b, a, msg) 305 } 306} 307 308func normalizeContent(c string) string { 309 norm := c 310 norm = strings.Replace(norm, "\n", " ", -1) 311 norm = strings.Replace(norm, " ", " ", -1) 312 norm = strings.Replace(norm, " ", " ", -1) 313 norm = strings.Replace(norm, " ", " ", -1) 314 norm = strings.Replace(norm, "p> ", "p>", -1) 315 norm = strings.Replace(norm, "> <", "> <", -1) 316 return strings.TrimSpace(norm) 317} 318 319func checkPageTOC(t *testing.T, page page.Page, toc string) { 320 t.Helper() 321 if page.TableOfContents() != template.HTML(toc) { 322 t.Fatalf("Page TableOfContents is:\n%q.\nExpected %q", page.TableOfContents(), toc) 323 } 324} 325 326func checkPageSummary(t *testing.T, page page.Page, summary string, msg ...interface{}) { 327 a := normalizeContent(string(page.Summary())) 328 b := normalizeContent(summary) 329 if a != b { 330 t.Fatalf("Page summary is:\n%q.\nExpected\n%q (%q)", a, b, msg) 331 } 332} 333 334func checkPageType(t *testing.T, page page.Page, pageType string) { 335 if page.Type() != pageType { 336 t.Fatalf("Page type is: %s. Expected: %s", page.Type(), pageType) 337 } 338} 339 340func checkPageDate(t *testing.T, page page.Page, time time.Time) { 341 if page.Date() != time { 342 t.Fatalf("Page date is: %s. Expected: %s", page.Date(), time) 343 } 344} 345 346func normalizeExpected(ext, str string) string { 347 str = normalizeContent(str) 348 switch ext { 349 default: 350 return str 351 case "html": 352 return strings.Trim(helpers.StripHTML(str), " ") 353 case "ad": 354 paragraphs := strings.Split(str, "</p>") 355 expected := "" 356 for _, para := range paragraphs { 357 if para == "" { 358 continue 359 } 360 expected += fmt.Sprintf("<div class=\"paragraph\">\n%s</p></div>\n", para) 361 } 362 363 return expected 364 case "rst": 365 return fmt.Sprintf("<div class=\"document\">\n\n\n%s</div>", str) 366 } 367} 368 369func testAllMarkdownEnginesForPages(t *testing.T, 370 assertFunc func(t *testing.T, ext string, pages page.Pages), settings map[string]interface{}, pageSources ...string) { 371 372 engines := []struct { 373 ext string 374 shouldExecute func() bool 375 }{ 376 {"md", func() bool { return true }}, 377 {"mmark", func() bool { return true }}, 378 {"ad", func() bool { return asciidocext.Supports() }}, 379 {"rst", func() bool { return true }}, 380 } 381 382 for _, e := range engines { 383 if !e.shouldExecute() { 384 continue 385 } 386 387 t.Run(e.ext, func(t *testing.T) { 388 389 cfg, fs := newTestCfg(func(cfg config.Provider) error { 390 for k, v := range settings { 391 cfg.Set(k, v) 392 } 393 return nil 394 }) 395 396 contentDir := "content" 397 398 if s := cfg.GetString("contentDir"); s != "" { 399 contentDir = s 400 } 401 402 cfg.Set("security", map[string]interface{}{ 403 "exec": map[string]interface{}{ 404 "allow": []string{"^python$", "^rst2html.*", "^asciidoctor$"}, 405 }, 406 }) 407 408 var fileSourcePairs []string 409 410 for i, source := range pageSources { 411 fileSourcePairs = append(fileSourcePairs, fmt.Sprintf("p%d.%s", i, e.ext), source) 412 } 413 414 for i := 0; i < len(fileSourcePairs); i += 2 { 415 writeSource(t, fs, filepath.Join(contentDir, fileSourcePairs[i]), fileSourcePairs[i+1]) 416 } 417 418 // Add a content page for the home page 419 homePath := fmt.Sprintf("_index.%s", e.ext) 420 writeSource(t, fs, filepath.Join(contentDir, homePath), homePage) 421 422 b := newTestSitesBuilderFromDepsCfg(t, deps.DepsCfg{Fs: fs, Cfg: cfg}).WithNothingAdded() 423 b.Build(BuildCfg{}) 424 425 s := b.H.Sites[0] 426 427 b.Assert(len(s.RegularPages()), qt.Equals, len(pageSources)) 428 429 assertFunc(t, e.ext, s.RegularPages()) 430 431 home, err := s.Info.Home() 432 b.Assert(err, qt.IsNil) 433 b.Assert(home, qt.Not(qt.IsNil)) 434 b.Assert(home.File().Path(), qt.Equals, homePath) 435 b.Assert(content(home), qt.Contains, "Home Page Content") 436 437 }) 438 439 } 440} 441 442// Issue #1076 443func TestPageWithDelimiterForMarkdownThatCrossesBorder(t *testing.T) { 444 t.Parallel() 445 cfg, fs := newTestCfg() 446 447 c := qt.New(t) 448 449 writeSource(t, fs, filepath.Join("content", "simple.md"), simplePageWithSummaryDelimiterAndMarkdownThatCrossesBorder) 450 451 s := buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{SkipRender: true}) 452 453 c.Assert(len(s.RegularPages()), qt.Equals, 1) 454 455 p := s.RegularPages()[0] 456 457 if p.Summary() != template.HTML( 458 "<p>The <a href=\"http://gohugo.io/\">best static site generator</a>.<sup id=\"fnref:1\"><a href=\"#fn:1\" class=\"footnote-ref\" role=\"doc-noteref\">1</a></sup></p>") { 459 t.Fatalf("Got summary:\n%q", p.Summary()) 460 } 461 462 cnt := content(p) 463 if cnt != "<p>The <a href=\"http://gohugo.io/\">best static site generator</a>.<sup id=\"fnref:1\"><a href=\"#fn:1\" class=\"footnote-ref\" role=\"doc-noteref\">1</a></sup></p>\n<section class=\"footnotes\" role=\"doc-endnotes\">\n<hr>\n<ol>\n<li id=\"fn:1\" role=\"doc-endnote\">\n<p>Many people say so. <a href=\"#fnref:1\" class=\"footnote-backref\" role=\"doc-backlink\">↩︎</a></p>\n</li>\n</ol>\n</section>" { 464 t.Fatalf("Got content:\n%q", cnt) 465 } 466} 467 468func TestPageDatesAllKinds(t *testing.T) { 469 t.Parallel() 470 471 pageContent := ` 472--- 473title: Page 474date: 2017-01-15 475tags: ["hugo"] 476categories: ["cool stuff"] 477--- 478` 479 480 b := newTestSitesBuilder(t) 481 b.WithSimpleConfigFile().WithContent("page.md", pageContent) 482 b.WithContent("blog/page.md", pageContent) 483 484 b.CreateSites().Build(BuildCfg{}) 485 486 b.Assert(len(b.H.Sites), qt.Equals, 1) 487 s := b.H.Sites[0] 488 489 checkDate := func(t time.Time, msg string) { 490 b.Assert(t.Year(), qt.Equals, 2017, qt.Commentf(msg)) 491 } 492 493 checkDated := func(d resource.Dated, msg string) { 494 checkDate(d.Date(), "date: "+msg) 495 checkDate(d.Lastmod(), "lastmod: "+msg) 496 } 497 for _, p := range s.Pages() { 498 checkDated(p, p.Kind()) 499 } 500 checkDate(s.Info.LastChange(), "site") 501} 502 503func TestPageDatesSections(t *testing.T) { 504 t.Parallel() 505 506 b := newTestSitesBuilder(t) 507 b.WithSimpleConfigFile().WithContent("no-index/page.md", ` 508--- 509title: Page 510date: 2017-01-15 511--- 512`, "with-index-no-date/_index.md", `--- 513title: No Date 514--- 515 516`, 517 // https://github.com/gohugoio/hugo/issues/5854 518 "with-index-date/_index.md", `--- 519title: Date 520date: 2018-01-15 521--- 522 523`, "with-index-date/p1.md", `--- 524title: Date 525date: 2018-01-15 526--- 527 528`, "with-index-date/p1.md", `--- 529title: Date 530date: 2018-01-15 531--- 532 533`) 534 535 for i := 1; i <= 20; i++ { 536 b.WithContent(fmt.Sprintf("main-section/p%d.md", i), `--- 537title: Date 538date: 2012-01-12 539--- 540 541`) 542 } 543 544 b.CreateSites().Build(BuildCfg{}) 545 546 b.Assert(len(b.H.Sites), qt.Equals, 1) 547 s := b.H.Sites[0] 548 549 checkDate := func(p page.Page, year int) { 550 b.Assert(p.Date().Year(), qt.Equals, year) 551 b.Assert(p.Lastmod().Year(), qt.Equals, year) 552 } 553 554 checkDate(s.getPage("/"), 2018) 555 checkDate(s.getPage("/no-index"), 2017) 556 b.Assert(s.getPage("/with-index-no-date").Date().IsZero(), qt.Equals, true) 557 checkDate(s.getPage("/with-index-date"), 2018) 558 559 b.Assert(s.Site.LastChange().Year(), qt.Equals, 2018) 560} 561 562func TestCreateNewPage(t *testing.T) { 563 t.Parallel() 564 c := qt.New(t) 565 assertFunc := func(t *testing.T, ext string, pages page.Pages) { 566 p := pages[0] 567 568 // issue #2290: Path is relative to the content dir and will continue to be so. 569 c.Assert(p.File().Path(), qt.Equals, fmt.Sprintf("p0.%s", ext)) 570 c.Assert(p.IsHome(), qt.Equals, false) 571 checkPageTitle(t, p, "Simple") 572 checkPageContent(t, p, normalizeExpected(ext, "<p>Simple Page</p>\n")) 573 checkPageSummary(t, p, "Simple Page") 574 checkPageType(t, p, "page") 575 } 576 577 settings := map[string]interface{}{ 578 "contentDir": "mycontent", 579 } 580 581 testAllMarkdownEnginesForPages(t, assertFunc, settings, simplePage) 582} 583 584func TestPageSummary(t *testing.T) { 585 t.Parallel() 586 assertFunc := func(t *testing.T, ext string, pages page.Pages) { 587 p := pages[0] 588 checkPageTitle(t, p, "SimpleWithoutSummaryDelimiter") 589 // Source is not Asciidoctor- or RST-compatible so don't test them 590 if ext != "ad" && ext != "rst" { 591 checkPageContent(t, p, normalizeExpected(ext, "<p><a href=\"https://lipsum.com/\">Lorem ipsum</a> dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</p>\n\n<p>Additional text.</p>\n\n<p>Further text.</p>\n"), ext) 592 checkPageSummary(t, p, normalizeExpected(ext, "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Additional text."), ext) 593 } 594 checkPageType(t, p, "page") 595 } 596 597 testAllMarkdownEnginesForPages(t, assertFunc, nil, simplePageWithoutSummaryDelimiter) 598} 599 600func TestPageWithDelimiter(t *testing.T) { 601 t.Parallel() 602 assertFunc := func(t *testing.T, ext string, pages page.Pages) { 603 p := pages[0] 604 checkPageTitle(t, p, "Simple") 605 checkPageContent(t, p, normalizeExpected(ext, "<p>Summary Next Line</p>\n\n<p>Some more text</p>\n"), ext) 606 checkPageSummary(t, p, normalizeExpected(ext, "<p>Summary Next Line</p>"), ext) 607 checkPageType(t, p, "page") 608 } 609 610 testAllMarkdownEnginesForPages(t, assertFunc, nil, simplePageWithSummaryDelimiter) 611} 612 613func TestPageWithSummaryParameter(t *testing.T) { 614 t.Parallel() 615 assertFunc := func(t *testing.T, ext string, pages page.Pages) { 616 p := pages[0] 617 checkPageTitle(t, p, "SimpleWithSummaryParameter") 618 checkPageContent(t, p, normalizeExpected(ext, "<p>Some text.</p>\n\n<p>Some more text.</p>\n"), ext) 619 // Summary is not Asciidoctor- or RST-compatible so don't test them 620 if ext != "ad" && ext != "rst" { 621 checkPageSummary(t, p, normalizeExpected(ext, "Page with summary parameter and <a href=\"http://www.example.com/\">a link</a>"), ext) 622 } 623 checkPageType(t, p, "page") 624 } 625 626 testAllMarkdownEnginesForPages(t, assertFunc, nil, simplePageWithSummaryParameter) 627} 628 629// Issue #3854 630// Also see https://github.com/gohugoio/hugo/issues/3977 631func TestPageWithDateFields(t *testing.T) { 632 c := qt.New(t) 633 pageWithDate := `--- 634title: P%d 635weight: %d 636%s: 2017-10-13 637--- 638Simple Page With Some Date` 639 640 hasDate := func(p page.Page) bool { 641 return p.Date().Year() == 2017 642 } 643 644 datePage := func(field string, weight int) string { 645 return fmt.Sprintf(pageWithDate, weight, weight, field) 646 } 647 648 t.Parallel() 649 assertFunc := func(t *testing.T, ext string, pages page.Pages) { 650 c.Assert(len(pages) > 0, qt.Equals, true) 651 for _, p := range pages { 652 c.Assert(hasDate(p), qt.Equals, true) 653 } 654 } 655 656 fields := []string{"date", "publishdate", "pubdate", "published"} 657 pageContents := make([]string, len(fields)) 658 for i, field := range fields { 659 pageContents[i] = datePage(field, i+1) 660 } 661 662 testAllMarkdownEnginesForPages(t, assertFunc, nil, pageContents...) 663} 664 665// Issue #2601 666func TestPageRawContent(t *testing.T) { 667 t.Parallel() 668 cfg, fs := newTestCfg() 669 c := qt.New(t) 670 671 writeSource(t, fs, filepath.Join("content", "raw.md"), `--- 672title: Raw 673--- 674**Raw**`) 675 676 writeSource(t, fs, filepath.Join("layouts", "_default", "single.html"), `{{ .RawContent }}`) 677 678 s := buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{SkipRender: true}) 679 680 c.Assert(len(s.RegularPages()), qt.Equals, 1) 681 p := s.RegularPages()[0] 682 683 c.Assert("**Raw**", qt.Equals, p.RawContent()) 684} 685 686func TestPageWithShortCodeInSummary(t *testing.T) { 687 t.Parallel() 688 assertFunc := func(t *testing.T, ext string, pages page.Pages) { 689 p := pages[0] 690 checkPageTitle(t, p, "Simple") 691 checkPageContent(t, p, normalizeExpected(ext, "<p>Summary Next Line. <figure><img src=\"/not/real\"/> </figure> . More text here.</p><p>Some more text</p>")) 692 checkPageSummary(t, p, "Summary Next Line. . More text here. Some more text") 693 checkPageType(t, p, "page") 694 } 695 696 testAllMarkdownEnginesForPages(t, assertFunc, nil, simplePageWithShortcodeInSummary) 697} 698 699func TestPageWithAdditionalExtension(t *testing.T) { 700 t.Parallel() 701 cfg, fs := newTestCfg() 702 cfg.Set("markup", map[string]interface{}{ 703 "defaultMarkdownHandler": "blackfriday", // TODO(bep) 704 }) 705 706 c := qt.New(t) 707 708 writeSource(t, fs, filepath.Join("content", "simple.md"), simplePageWithAdditionalExtension) 709 710 s := buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{SkipRender: true}) 711 712 c.Assert(len(s.RegularPages()), qt.Equals, 1) 713 714 p := s.RegularPages()[0] 715 716 checkPageContent(t, p, "<p>first line.<br />\nsecond line.</p>\n\n<p>fourth line.</p>\n") 717} 718 719func TestTableOfContents(t *testing.T) { 720 cfg, fs := newTestCfg() 721 c := qt.New(t) 722 723 writeSource(t, fs, filepath.Join("content", "tocpage.md"), pageWithToC) 724 725 s := buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{SkipRender: true}) 726 727 c.Assert(len(s.RegularPages()), qt.Equals, 1) 728 729 p := s.RegularPages()[0] 730 731 checkPageContent(t, p, "<p>For some moments the old man did not reply. He stood with bowed head, buried in deep thought. But at last he spoke.</p><h2 id=\"aa\">AA</h2> <p>I have no idea, of course, how long it took me to reach the limit of the plain, but at last I entered the foothills, following a pretty little canyon upward toward the mountains. Beside me frolicked a laughing brooklet, hurrying upon its noisy way down to the silent sea. In its quieter pools I discovered many small fish, of four-or five-pound weight I should imagine. In appearance, except as to size and color, they were not unlike the whale of our own seas. As I watched them playing about I discovered, not only that they suckled their young, but that at intervals they rose to the surface to breathe as well as to feed upon certain grasses and a strange, scarlet lichen which grew upon the rocks just above the water line.</p><h3 id=\"aaa\">AAA</h3> <p>I remember I felt an extraordinary persuasion that I was being played with, that presently, when I was upon the very verge of safety, this mysterious death–as swift as the passage of light–would leap after me from the pit about the cylinder and strike me down. ## BB</p><h3 id=\"bbb\">BBB</h3> <p>“You’re a great Granser,” he cried delightedly, “always making believe them little marks mean something.”</p>") 732 checkPageTOC(t, p, "<nav id=\"TableOfContents\">\n <ul>\n <li><a href=\"#aa\">AA</a>\n <ul>\n <li><a href=\"#aaa\">AAA</a></li>\n <li><a href=\"#bbb\">BBB</a></li>\n </ul>\n </li>\n </ul>\n</nav>") 733} 734 735func TestPageWithMoreTag(t *testing.T) { 736 t.Parallel() 737 assertFunc := func(t *testing.T, ext string, pages page.Pages) { 738 p := pages[0] 739 checkPageTitle(t, p, "Simple") 740 checkPageContent(t, p, normalizeExpected(ext, "<p>Summary Same Line</p>\n\n<p>Some more text</p>\n")) 741 checkPageSummary(t, p, normalizeExpected(ext, "<p>Summary Same Line</p>")) 742 checkPageType(t, p, "page") 743 } 744 745 testAllMarkdownEnginesForPages(t, assertFunc, nil, simplePageWithSummaryDelimiterSameLine) 746} 747 748// #2973 749func TestSummaryWithHTMLTagsOnNextLine(t *testing.T) { 750 assertFunc := func(t *testing.T, ext string, pages page.Pages) { 751 c := qt.New(t) 752 p := pages[0] 753 s := string(p.Summary()) 754 c.Assert(s, qt.Contains, "Happy new year everyone!") 755 c.Assert(s, qt.Not(qt.Contains), "User interface") 756 } 757 758 testAllMarkdownEnginesForPages(t, assertFunc, nil, `--- 759title: Simple 760--- 761Happy new year everyone! 762 763Here is the last report for commits in the year 2016. It covers hrev50718-hrev50829. 764 765<!--more--> 766 767<h3>User interface</h3> 768 769`) 770} 771 772func TestPageWithDate(t *testing.T) { 773 t.Parallel() 774 cfg, fs := newTestCfg() 775 c := qt.New(t) 776 777 writeSource(t, fs, filepath.Join("content", "simple.md"), simplePageRFC3339Date) 778 779 s := buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{SkipRender: true}) 780 781 c.Assert(len(s.RegularPages()), qt.Equals, 1) 782 783 p := s.RegularPages()[0] 784 d, _ := time.Parse(time.RFC3339, "2013-05-17T16:59:30Z") 785 786 checkPageDate(t, p, d) 787} 788 789func TestPageWithLastmodFromGitInfo(t *testing.T) { 790 if htesting.IsCI() { 791 // TODO(bep) figure out why this fails on GitHub actions. 792 t.Skip("Skip GitInfo test on CI") 793 } 794 c := qt.New(t) 795 796 // We need to use the OS fs for this. 797 cfg := config.New() 798 fs := hugofs.NewFrom(hugofs.Os, cfg) 799 fs.Destination = &afero.MemMapFs{} 800 801 wd, err := os.Getwd() 802 c.Assert(err, qt.IsNil) 803 804 cfg.Set("frontmatter", map[string]interface{}{ 805 "lastmod": []string{":git", "lastmod"}, 806 }) 807 cfg.Set("defaultContentLanguage", "en") 808 809 langConfig := map[string]interface{}{ 810 "en": map[string]interface{}{ 811 "weight": 1, 812 "languageName": "English", 813 "contentDir": "content", 814 }, 815 "nn": map[string]interface{}{ 816 "weight": 2, 817 "languageName": "Nynorsk", 818 "contentDir": "content_nn", 819 }, 820 } 821 822 cfg.Set("languages", langConfig) 823 cfg.Set("enableGitInfo", true) 824 825 cfg.Set("workingDir", filepath.Join(wd, "testsite")) 826 827 b := newTestSitesBuilderFromDepsCfg(t, deps.DepsCfg{Fs: fs, Cfg: cfg}).WithNothingAdded() 828 829 b.Build(BuildCfg{SkipRender: true}) 830 h := b.H 831 832 c.Assert(len(h.Sites), qt.Equals, 2) 833 834 enSite := h.Sites[0] 835 c.Assert(len(enSite.RegularPages()), qt.Equals, 1) 836 837 // 2018-03-11 is the Git author date for testsite/content/first-post.md 838 c.Assert(enSite.RegularPages()[0].Lastmod().Format("2006-01-02"), qt.Equals, "2018-03-11") 839 840 nnSite := h.Sites[1] 841 c.Assert(len(nnSite.RegularPages()), qt.Equals, 1) 842 843 // 2018-08-11 is the Git author date for testsite/content_nn/first-post.md 844 c.Assert(nnSite.RegularPages()[0].Lastmod().Format("2006-01-02"), qt.Equals, "2018-08-11") 845} 846 847func TestPageWithFrontMatterConfig(t *testing.T) { 848 for _, dateHandler := range []string{":filename", ":fileModTime"} { 849 dateHandler := dateHandler 850 t.Run(fmt.Sprintf("dateHandler=%q", dateHandler), func(t *testing.T) { 851 t.Parallel() 852 c := qt.New(t) 853 cfg, fs := newTestCfg() 854 855 pageTemplate := ` 856--- 857title: Page 858weight: %d 859lastMod: 2018-02-28 860%s 861--- 862Content 863` 864 865 cfg.Set("frontmatter", map[string]interface{}{ 866 "date": []string{dateHandler, "date"}, 867 }) 868 869 c1 := filepath.Join("content", "section", "2012-02-21-noslug.md") 870 c2 := filepath.Join("content", "section", "2012-02-22-slug.md") 871 872 writeSource(t, fs, c1, fmt.Sprintf(pageTemplate, 1, "")) 873 writeSource(t, fs, c2, fmt.Sprintf(pageTemplate, 2, "slug: aslug")) 874 875 c1fi, err := fs.Source.Stat(c1) 876 c.Assert(err, qt.IsNil) 877 c2fi, err := fs.Source.Stat(c2) 878 c.Assert(err, qt.IsNil) 879 880 b := newTestSitesBuilderFromDepsCfg(t, deps.DepsCfg{Fs: fs, Cfg: cfg}).WithNothingAdded() 881 b.Build(BuildCfg{SkipRender: true}) 882 883 s := b.H.Sites[0] 884 c.Assert(len(s.RegularPages()), qt.Equals, 2) 885 886 noSlug := s.RegularPages()[0] 887 slug := s.RegularPages()[1] 888 889 c.Assert(noSlug.Lastmod().Day(), qt.Equals, 28) 890 891 switch strings.ToLower(dateHandler) { 892 case ":filename": 893 c.Assert(noSlug.Date().IsZero(), qt.Equals, false) 894 c.Assert(slug.Date().IsZero(), qt.Equals, false) 895 c.Assert(noSlug.Date().Year(), qt.Equals, 2012) 896 c.Assert(slug.Date().Year(), qt.Equals, 2012) 897 c.Assert(noSlug.Slug(), qt.Equals, "noslug") 898 c.Assert(slug.Slug(), qt.Equals, "aslug") 899 case ":filemodtime": 900 c.Assert(noSlug.Date().Year(), qt.Equals, c1fi.ModTime().Year()) 901 c.Assert(slug.Date().Year(), qt.Equals, c2fi.ModTime().Year()) 902 fallthrough 903 default: 904 c.Assert(noSlug.Slug(), qt.Equals, "") 905 c.Assert(slug.Slug(), qt.Equals, "aslug") 906 907 } 908 }) 909 } 910} 911 912func TestWordCountWithAllCJKRunesWithoutHasCJKLanguage(t *testing.T) { 913 t.Parallel() 914 assertFunc := func(t *testing.T, ext string, pages page.Pages) { 915 p := pages[0] 916 if p.WordCount() != 8 { 917 t.Fatalf("[%s] incorrect word count. expected %v, got %v", ext, 8, p.WordCount()) 918 } 919 } 920 921 testAllMarkdownEnginesForPages(t, assertFunc, nil, simplePageWithAllCJKRunes) 922} 923 924func TestWordCountWithAllCJKRunesHasCJKLanguage(t *testing.T) { 925 t.Parallel() 926 settings := map[string]interface{}{"hasCJKLanguage": true} 927 928 assertFunc := func(t *testing.T, ext string, pages page.Pages) { 929 p := pages[0] 930 if p.WordCount() != 15 { 931 t.Fatalf("[%s] incorrect word count, expected %v, got %v", ext, 15, p.WordCount()) 932 } 933 } 934 testAllMarkdownEnginesForPages(t, assertFunc, settings, simplePageWithAllCJKRunes) 935} 936 937func TestWordCountWithMainEnglishWithCJKRunes(t *testing.T) { 938 t.Parallel() 939 settings := map[string]interface{}{"hasCJKLanguage": true} 940 941 assertFunc := func(t *testing.T, ext string, pages page.Pages) { 942 p := pages[0] 943 if p.WordCount() != 74 { 944 t.Fatalf("[%s] incorrect word count, expected %v, got %v", ext, 74, p.WordCount()) 945 } 946 947 if p.Summary() != simplePageWithMainEnglishWithCJKRunesSummary { 948 t.Fatalf("[%s] incorrect Summary for content '%s'. expected %v, got %v", ext, p.Plain(), 949 simplePageWithMainEnglishWithCJKRunesSummary, p.Summary()) 950 } 951 } 952 953 testAllMarkdownEnginesForPages(t, assertFunc, settings, simplePageWithMainEnglishWithCJKRunes) 954} 955 956func TestWordCountWithIsCJKLanguageFalse(t *testing.T) { 957 t.Parallel() 958 settings := map[string]interface{}{ 959 "hasCJKLanguage": true, 960 } 961 962 assertFunc := func(t *testing.T, ext string, pages page.Pages) { 963 p := pages[0] 964 if p.WordCount() != 75 { 965 t.Fatalf("[%s] incorrect word count for content '%s'. expected %v, got %v", ext, p.Plain(), 74, p.WordCount()) 966 } 967 968 if p.Summary() != simplePageWithIsCJKLanguageFalseSummary { 969 t.Fatalf("[%s] incorrect Summary for content '%s'. expected %v, got %v", ext, p.Plain(), 970 simplePageWithIsCJKLanguageFalseSummary, p.Summary()) 971 } 972 } 973 974 testAllMarkdownEnginesForPages(t, assertFunc, settings, simplePageWithIsCJKLanguageFalse) 975} 976 977func TestWordCount(t *testing.T) { 978 t.Parallel() 979 assertFunc := func(t *testing.T, ext string, pages page.Pages) { 980 p := pages[0] 981 if p.WordCount() != 483 { 982 t.Fatalf("[%s] incorrect word count. expected %v, got %v", ext, 483, p.WordCount()) 983 } 984 985 if p.FuzzyWordCount() != 500 { 986 t.Fatalf("[%s] incorrect word count. expected %v, got %v", ext, 500, p.FuzzyWordCount()) 987 } 988 989 if p.ReadingTime() != 3 { 990 t.Fatalf("[%s] incorrect min read. expected %v, got %v", ext, 3, p.ReadingTime()) 991 } 992 } 993 994 testAllMarkdownEnginesForPages(t, assertFunc, nil, simplePageWithLongContent) 995} 996 997func TestPagePaths(t *testing.T) { 998 t.Parallel() 999 c := qt.New(t) 1000 1001 siteParmalinksSetting := map[string]string{ 1002 "post": ":year/:month/:day/:title/", 1003 } 1004 1005 tests := []struct { 1006 content string 1007 path string 1008 hasPermalink bool 1009 expected string 1010 }{ 1011 {simplePage, "post/x.md", false, "post/x.html"}, 1012 {simplePageWithURL, "post/x.md", false, "simple/url/index.html"}, 1013 {simplePageWithSlug, "post/x.md", false, "post/simple-slug.html"}, 1014 {simplePageWithDate, "post/x.md", true, "2013/10/15/simple/index.html"}, 1015 {UTF8Page, "post/x.md", false, "post/x.html"}, 1016 {UTF8PageWithURL, "post/x.md", false, "ラーメン/url/index.html"}, 1017 {UTF8PageWithSlug, "post/x.md", false, "post/ラーメン-slug.html"}, 1018 {UTF8PageWithDate, "post/x.md", true, "2013/10/15/ラーメン/index.html"}, 1019 } 1020 1021 for _, test := range tests { 1022 cfg, fs := newTestCfg() 1023 1024 if test.hasPermalink { 1025 cfg.Set("permalinks", siteParmalinksSetting) 1026 } 1027 1028 writeSource(t, fs, filepath.Join("content", filepath.FromSlash(test.path)), test.content) 1029 1030 s := buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{SkipRender: true}) 1031 c.Assert(len(s.RegularPages()), qt.Equals, 1) 1032 1033 } 1034} 1035 1036func TestTranslationKey(t *testing.T) { 1037 t.Parallel() 1038 c := qt.New(t) 1039 cfg, fs := newTestCfg() 1040 1041 writeSource(t, fs, filepath.Join("content", filepath.FromSlash("sect/simple.no.md")), "---\ntitle: \"A1\"\ntranslationKey: \"k1\"\n---\nContent\n") 1042 writeSource(t, fs, filepath.Join("content", filepath.FromSlash("sect/simple.en.md")), "---\ntitle: \"A2\"\n---\nContent\n") 1043 1044 s := buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{SkipRender: true}) 1045 1046 c.Assert(len(s.RegularPages()), qt.Equals, 2) 1047 1048 home, _ := s.Info.Home() 1049 c.Assert(home, qt.Not(qt.IsNil)) 1050 c.Assert(home.TranslationKey(), qt.Equals, "home") 1051 c.Assert(s.RegularPages()[0].TranslationKey(), qt.Equals, "page/k1") 1052 p2 := s.RegularPages()[1] 1053 1054 c.Assert(p2.TranslationKey(), qt.Equals, "page/sect/simple") 1055} 1056 1057func TestChompBOM(t *testing.T) { 1058 t.Parallel() 1059 c := qt.New(t) 1060 const utf8BOM = "\xef\xbb\xbf" 1061 1062 cfg, fs := newTestCfg() 1063 1064 writeSource(t, fs, filepath.Join("content", "simple.md"), utf8BOM+simplePage) 1065 1066 s := buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{SkipRender: true}) 1067 1068 c.Assert(len(s.RegularPages()), qt.Equals, 1) 1069 1070 p := s.RegularPages()[0] 1071 1072 checkPageTitle(t, p, "Simple") 1073} 1074 1075func TestPageWithEmoji(t *testing.T) { 1076 for _, enableEmoji := range []bool{true, false} { 1077 v := config.New() 1078 v.Set("enableEmoji", enableEmoji) 1079 1080 b := newTestSitesBuilder(t).WithViper(v) 1081 1082 b.WithContent("page-emoji.md", `--- 1083title: "Hugo Smile" 1084--- 1085This is a :smile:. 1086<!--more--> 1087 1088Another :smile: This is :not: :an: :emoji:. 1089 1090O :christmas_tree: 1091 1092Write me an :e-mail: or :email:? 1093 1094Too many colons: :: ::: :::: :?: :!: :.: 1095 1096If you dislike this video, you can hit that :-1: button :stuck_out_tongue_winking_eye:, 1097but if you like it, hit :+1: and get subscribed! 1098`) 1099 1100 b.CreateSites().Build(BuildCfg{}) 1101 1102 if enableEmoji { 1103 b.AssertFileContent("public/page-emoji/index.html", 1104 "This is a ", 1105 "Another ", 1106 "This is :not: :an: :emoji:.", 1107 "O ", 1108 "Write me an or ✉️?", 1109 "Too many colons: :: ::: :::: :?: :!: :.:", 1110 "you can hit that button ,", 1111 "hit and get subscribed!", 1112 ) 1113 } else { 1114 b.AssertFileContent("public/page-emoji/index.html", 1115 "This is a :smile:", 1116 "Another :smile:", 1117 "This is :not: :an: :emoji:.", 1118 "O :christmas_tree:", 1119 "Write me an :e-mail: or :email:?", 1120 "Too many colons: :: ::: :::: :?: :!: :.:", 1121 "you can hit that :-1: button :stuck_out_tongue_winking_eye:,", 1122 "hit :+1: and get subscribed!", 1123 ) 1124 } 1125 1126 } 1127} 1128 1129func TestPageHTMLContent(t *testing.T) { 1130 b := newTestSitesBuilder(t) 1131 b.WithSimpleConfigFile() 1132 1133 frontmatter := `--- 1134title: "HTML Content" 1135--- 1136` 1137 b.WithContent("regular.html", frontmatter+`<h1>Hugo</h1>`) 1138 b.WithContent("noblackfridayforyou.html", frontmatter+`**Hugo!**`) 1139 b.WithContent("manualsummary.html", frontmatter+` 1140<p>This is summary</p> 1141<!--more--> 1142<p>This is the main content.</p>`) 1143 1144 b.Build(BuildCfg{}) 1145 1146 b.AssertFileContent( 1147 "public/regular/index.html", 1148 "Single: HTML Content|Hello|en|RelPermalink: /regular/|", 1149 "Summary: Hugo|Truncated: false") 1150 1151 b.AssertFileContent( 1152 "public/noblackfridayforyou/index.html", 1153 "Permalink: http://example.com/noblackfridayforyou/|**Hugo!**|", 1154 ) 1155 1156 // https://github.com/gohugoio/hugo/issues/5723 1157 b.AssertFileContent( 1158 "public/manualsummary/index.html", 1159 "Single: HTML Content|Hello|en|RelPermalink: /manualsummary/|", 1160 "Summary: \n<p>This is summary</p>\n|Truncated: true", 1161 "|<p>This is the main content.</p>|", 1162 ) 1163} 1164 1165// https://github.com/gohugoio/hugo/issues/5381 1166func TestPageManualSummary(t *testing.T) { 1167 b := newTestSitesBuilder(t) 1168 b.WithSimpleConfigFile() 1169 1170 b.WithContent("page-md-shortcode.md", `--- 1171title: "Hugo" 1172--- 1173This is a {{< sc >}}. 1174<!--more--> 1175Content. 1176`) 1177 1178 // https://github.com/gohugoio/hugo/issues/5464 1179 b.WithContent("page-md-only-shortcode.md", `--- 1180title: "Hugo" 1181--- 1182{{< sc >}} 1183<!--more--> 1184{{< sc >}} 1185`) 1186 1187 b.WithContent("page-md-shortcode-same-line.md", `--- 1188title: "Hugo" 1189--- 1190This is a {{< sc >}}<!--more-->Same line. 1191`) 1192 1193 b.WithContent("page-md-shortcode-same-line-after.md", `--- 1194title: "Hugo" 1195--- 1196Summary<!--more-->{{< sc >}} 1197`) 1198 1199 b.WithContent("page-org-shortcode.org", `#+TITLE: T1 1200#+AUTHOR: A1 1201#+DESCRIPTION: D1 1202This is a {{< sc >}}. 1203# more 1204Content. 1205`) 1206 1207 b.WithContent("page-org-variant1.org", `#+TITLE: T1 1208Summary. 1209 1210# more 1211 1212Content. 1213`) 1214 1215 b.WithTemplatesAdded("layouts/shortcodes/sc.html", "a shortcode") 1216 b.WithTemplatesAdded("layouts/_default/single.html", ` 1217SUMMARY:{{ .Summary }}:END 1218-------------------------- 1219CONTENT:{{ .Content }} 1220`) 1221 1222 b.CreateSites().Build(BuildCfg{}) 1223 1224 b.AssertFileContent("public/page-md-shortcode/index.html", 1225 "SUMMARY:<p>This is a a shortcode.</p>:END", 1226 "CONTENT:<p>This is a a shortcode.</p>\n\n<p>Content.</p>\n", 1227 ) 1228 1229 b.AssertFileContent("public/page-md-shortcode-same-line/index.html", 1230 "SUMMARY:<p>This is a a shortcode</p>:END", 1231 "CONTENT:<p>This is a a shortcode</p>\n\n<p>Same line.</p>\n", 1232 ) 1233 1234 b.AssertFileContent("public/page-md-shortcode-same-line-after/index.html", 1235 "SUMMARY:<p>Summary</p>:END", 1236 "CONTENT:<p>Summary</p>\n\na shortcode", 1237 ) 1238 1239 b.AssertFileContent("public/page-org-shortcode/index.html", 1240 "SUMMARY:<p>\nThis is a a shortcode.\n</p>:END", 1241 "CONTENT:<p>\nThis is a a shortcode.\n</p>\n<p>\nContent.\t\n</p>\n", 1242 ) 1243 b.AssertFileContent("public/page-org-variant1/index.html", 1244 "SUMMARY:<p>\nSummary.\n</p>:END", 1245 "CONTENT:<p>\nSummary.\n</p>\n<p>\nContent.\t\n</p>\n", 1246 ) 1247 1248 b.AssertFileContent("public/page-md-only-shortcode/index.html", 1249 "SUMMARY:a shortcode:END", 1250 "CONTENT:a shortcode\n\na shortcode\n", 1251 ) 1252} 1253 1254// https://github.com/gohugoio/hugo/issues/5478 1255func TestPageWithCommentedOutFrontMatter(t *testing.T) { 1256 b := newTestSitesBuilder(t) 1257 b.WithSimpleConfigFile() 1258 1259 b.WithContent("page.md", `<!-- 1260+++ 1261title = "hello" 1262+++ 1263--> 1264This is the content. 1265`) 1266 1267 b.WithTemplatesAdded("layouts/_default/single.html", ` 1268Title: {{ .Title }} 1269Content:{{ .Content }} 1270`) 1271 1272 b.CreateSites().Build(BuildCfg{}) 1273 1274 b.AssertFileContent("public/page/index.html", 1275 "Title: hello", 1276 "Content:<p>This is the content.</p>", 1277 ) 1278} 1279 1280// https://github.com/gohugoio/hugo/issues/5781 1281func TestPageWithZeroFile(t *testing.T) { 1282 newTestSitesBuilder(t).WithLogger(loggers.NewWarningLogger()).WithSimpleConfigFile(). 1283 WithTemplatesAdded("index.html", "{{ .File.Filename }}{{ with .File }}{{ .Dir }}{{ end }}").Build(BuildCfg{}) 1284} 1285 1286func TestHomePageWithNoTitle(t *testing.T) { 1287 b := newTestSitesBuilder(t).WithConfigFile("toml", ` 1288title = "Site Title" 1289`) 1290 b.WithTemplatesAdded("index.html", "Title|{{ with .Title }}{{ . }}{{ end }}|") 1291 b.WithContent("_index.md", `--- 1292description: "No title for you!" 1293--- 1294 1295Content. 1296`) 1297 1298 b.Build(BuildCfg{}) 1299 b.AssertFileContent("public/index.html", "Title||") 1300} 1301 1302func TestShouldBuild(t *testing.T) { 1303 t.Parallel() 1304 past := time.Date(2009, 11, 17, 20, 34, 58, 651387237, time.UTC) 1305 future := time.Date(2037, 11, 17, 20, 34, 58, 651387237, time.UTC) 1306 zero := time.Time{} 1307 1308 publishSettings := []struct { 1309 buildFuture bool 1310 buildExpired bool 1311 buildDrafts bool 1312 draft bool 1313 publishDate time.Time 1314 expiryDate time.Time 1315 out bool 1316 }{ 1317 // publishDate and expiryDate 1318 {false, false, false, false, zero, zero, true}, 1319 {false, false, false, false, zero, future, true}, 1320 {false, false, false, false, past, zero, true}, 1321 {false, false, false, false, past, future, true}, 1322 {false, false, false, false, past, past, false}, 1323 {false, false, false, false, future, future, false}, 1324 {false, false, false, false, future, past, false}, 1325 1326 // buildFuture and buildExpired 1327 {false, true, false, false, past, past, true}, 1328 {true, true, false, false, past, past, true}, 1329 {true, false, false, false, past, past, false}, 1330 {true, false, false, false, future, future, true}, 1331 {true, true, false, false, future, future, true}, 1332 {false, true, false, false, future, past, false}, 1333 1334 // buildDrafts and draft 1335 {true, true, false, true, past, future, false}, 1336 {true, true, true, true, past, future, true}, 1337 {true, true, true, true, past, future, true}, 1338 } 1339 1340 for _, ps := range publishSettings { 1341 s := shouldBuild(ps.buildFuture, ps.buildExpired, ps.buildDrafts, ps.draft, 1342 ps.publishDate, ps.expiryDate) 1343 if s != ps.out { 1344 t.Errorf("AssertShouldBuild unexpected output with params: %+v", ps) 1345 } 1346 } 1347} 1348 1349// "dot" in path: #1885 and #2110 1350// disablePathToLower regression: #3374 1351func TestPathIssues(t *testing.T) { 1352 for _, disablePathToLower := range []bool{false, true} { 1353 for _, uglyURLs := range []bool{false, true} { 1354 disablePathToLower := disablePathToLower 1355 uglyURLs := uglyURLs 1356 t.Run(fmt.Sprintf("disablePathToLower=%t,uglyURLs=%t", disablePathToLower, uglyURLs), func(t *testing.T) { 1357 t.Parallel() 1358 cfg, fs := newTestCfg() 1359 th := newTestHelper(cfg, fs, t) 1360 c := qt.New(t) 1361 1362 cfg.Set("permalinks", map[string]string{ 1363 "post": ":section/:title", 1364 }) 1365 1366 cfg.Set("uglyURLs", uglyURLs) 1367 cfg.Set("disablePathToLower", disablePathToLower) 1368 cfg.Set("paginate", 1) 1369 1370 writeSource(t, fs, filepath.Join("layouts", "_default", "single.html"), "<html><body>{{.Content}}</body></html>") 1371 writeSource(t, fs, filepath.Join("layouts", "_default", "list.html"), 1372 "<html><body>P{{.Paginator.PageNumber}}|URL: {{.Paginator.URL}}|{{ if .Paginator.HasNext }}Next: {{.Paginator.Next.URL }}{{ end }}</body></html>") 1373 1374 for i := 0; i < 3; i++ { 1375 writeSource(t, fs, filepath.Join("content", "post", fmt.Sprintf("doc%d.md", i)), 1376 fmt.Sprintf(`--- 1377title: "test%d.dot" 1378tags: 1379- ".net" 1380--- 1381# doc1 1382*some content*`, i)) 1383 } 1384 1385 writeSource(t, fs, filepath.Join("content", "Blog", "Blog1.md"), 1386 fmt.Sprintf(`--- 1387title: "testBlog" 1388tags: 1389- "Blog" 1390--- 1391# doc1 1392*some blog content*`)) 1393 1394 s := buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{}) 1395 1396 c.Assert(len(s.RegularPages()), qt.Equals, 4) 1397 1398 pathFunc := func(s string) string { 1399 if uglyURLs { 1400 return strings.Replace(s, "/index.html", ".html", 1) 1401 } 1402 return s 1403 } 1404 1405 blog := "blog" 1406 1407 if disablePathToLower { 1408 blog = "Blog" 1409 } 1410 1411 th.assertFileContent(pathFunc("public/"+blog+"/"+blog+"1/index.html"), "some blog content") 1412 1413 th.assertFileContent(pathFunc("public/post/test0.dot/index.html"), "some content") 1414 1415 if uglyURLs { 1416 th.assertFileContent("public/post/page/1.html", `canonical" href="/post.html"/`) 1417 th.assertFileContent("public/post.html", `<body>P1|URL: /post.html|Next: /post/page/2.html</body>`) 1418 th.assertFileContent("public/post/page/2.html", `<body>P2|URL: /post/page/2.html|Next: /post/page/3.html</body>`) 1419 } else { 1420 th.assertFileContent("public/post/page/1/index.html", `canonical" href="/post/"/`) 1421 th.assertFileContent("public/post/index.html", `<body>P1|URL: /post/|Next: /post/page/2/</body>`) 1422 th.assertFileContent("public/post/page/2/index.html", `<body>P2|URL: /post/page/2/|Next: /post/page/3/</body>`) 1423 th.assertFileContent("public/tags/.net/index.html", `<body>P1|URL: /tags/.net/|Next: /tags/.net/page/2/</body>`) 1424 1425 } 1426 1427 p := s.RegularPages()[0] 1428 if uglyURLs { 1429 c.Assert(p.RelPermalink(), qt.Equals, "/post/test0.dot.html") 1430 } else { 1431 c.Assert(p.RelPermalink(), qt.Equals, "/post/test0.dot/") 1432 } 1433 }) 1434 } 1435 } 1436} 1437 1438// https://github.com/gohugoio/hugo/issues/4675 1439func TestWordCountAndSimilarVsSummary(t *testing.T) { 1440 t.Parallel() 1441 c := qt.New(t) 1442 1443 single := []string{"_default/single.html", ` 1444WordCount: {{ .WordCount }} 1445FuzzyWordCount: {{ .FuzzyWordCount }} 1446ReadingTime: {{ .ReadingTime }} 1447Len Plain: {{ len .Plain }} 1448Len PlainWords: {{ len .PlainWords }} 1449Truncated: {{ .Truncated }} 1450Len Summary: {{ len .Summary }} 1451Len Content: {{ len .Content }} 1452 1453SUMMARY:{{ .Summary }}:{{ len .Summary }}:END 1454`} 1455 1456 b := newTestSitesBuilder(t) 1457 b.WithSimpleConfigFile().WithTemplatesAdded(single...).WithContent("p1.md", fmt.Sprintf(`--- 1458title: p1 1459--- 1460 1461%s 1462 1463`, strings.Repeat("word ", 510)), 1464 1465 "p2.md", fmt.Sprintf(`--- 1466title: p2 1467--- 1468This is a summary. 1469 1470<!--more--> 1471 1472%s 1473 1474`, strings.Repeat("word ", 310)), 1475 "p3.md", fmt.Sprintf(`--- 1476title: p3 1477isCJKLanguage: true 1478--- 1479Summary: In Chinese, 好 means good. 1480 1481<!--more--> 1482 1483%s 1484 1485`, strings.Repeat("好", 200)), 1486 "p4.md", fmt.Sprintf(`--- 1487title: p4 1488isCJKLanguage: false 1489--- 1490Summary: In Chinese, 好 means good. 1491 1492<!--more--> 1493 1494%s 1495 1496`, strings.Repeat("好", 200)), 1497 1498 "p5.md", fmt.Sprintf(`--- 1499title: p4 1500isCJKLanguage: true 1501--- 1502Summary: In Chinese, 好 means good. 1503 1504%s 1505 1506`, strings.Repeat("好", 200)), 1507 "p6.md", fmt.Sprintf(`--- 1508title: p4 1509isCJKLanguage: false 1510--- 1511Summary: In Chinese, 好 means good. 1512 1513%s 1514 1515`, strings.Repeat("好", 200)), 1516 ) 1517 1518 b.CreateSites().Build(BuildCfg{}) 1519 1520 c.Assert(len(b.H.Sites), qt.Equals, 1) 1521 c.Assert(len(b.H.Sites[0].RegularPages()), qt.Equals, 6) 1522 1523 b.AssertFileContent("public/p1/index.html", "WordCount: 510\nFuzzyWordCount: 600\nReadingTime: 3\nLen Plain: 2550\nLen PlainWords: 510\nTruncated: false\nLen Summary: 2549\nLen Content: 2557") 1524 1525 b.AssertFileContent("public/p2/index.html", "WordCount: 314\nFuzzyWordCount: 400\nReadingTime: 2\nLen Plain: 1569\nLen PlainWords: 314\nTruncated: true\nLen Summary: 25\nLen Content: 1582") 1526 1527 b.AssertFileContent("public/p3/index.html", "WordCount: 206\nFuzzyWordCount: 300\nReadingTime: 1\nLen Plain: 638\nLen PlainWords: 7\nTruncated: true\nLen Summary: 43\nLen Content: 651") 1528 b.AssertFileContent("public/p4/index.html", "WordCount: 7\nFuzzyWordCount: 100\nReadingTime: 1\nLen Plain: 638\nLen PlainWords: 7\nTruncated: true\nLen Summary: 43\nLen Content: 651") 1529 b.AssertFileContent("public/p5/index.html", "WordCount: 206\nFuzzyWordCount: 300\nReadingTime: 1\nLen Plain: 638\nLen PlainWords: 7\nTruncated: true\nLen Summary: 229\nLen Content: 652") 1530 b.AssertFileContent("public/p6/index.html", "WordCount: 7\nFuzzyWordCount: 100\nReadingTime: 1\nLen Plain: 638\nLen PlainWords: 7\nTruncated: false\nLen Summary: 637\nLen Content: 652") 1531} 1532 1533func TestScratchSite(t *testing.T) { 1534 t.Parallel() 1535 1536 b := newTestSitesBuilder(t) 1537 b.WithSimpleConfigFile().WithTemplatesAdded("index.html", ` 1538{{ .Scratch.Set "b" "bv" }} 1539B: {{ .Scratch.Get "b" }} 1540`, 1541 "shortcodes/scratch.html", ` 1542{{ .Scratch.Set "c" "cv" }} 1543C: {{ .Scratch.Get "c" }} 1544`, 1545 ) 1546 1547 b.WithContentAdded("scratchme.md", ` 1548--- 1549title: Scratch Me! 1550--- 1551 1552{{< scratch >}} 1553`) 1554 b.Build(BuildCfg{}) 1555 1556 b.AssertFileContent("public/index.html", "B: bv") 1557 b.AssertFileContent("public/scratchme/index.html", "C: cv") 1558} 1559 1560func TestPageParam(t *testing.T) { 1561 t.Parallel() 1562 1563 b := newTestSitesBuilder(t).WithConfigFile("toml", ` 1564 1565baseURL = "https://example.org" 1566 1567[params] 1568[params.author] 1569 name = "Kurt Vonnegut" 1570 1571`) 1572 b.WithTemplatesAdded("index.html", ` 1573 1574{{ $withParam := .Site.GetPage "withparam" }} 1575{{ $noParam := .Site.GetPage "noparam" }} 1576{{ $withStringParam := .Site.GetPage "withstringparam" }} 1577 1578Author page: {{ $withParam.Param "author.name" }} 1579Author name page string: {{ $withStringParam.Param "author.name" }}| 1580Author page string: {{ $withStringParam.Param "author" }}| 1581Author site config: {{ $noParam.Param "author.name" }} 1582 1583`, 1584 ) 1585 1586 b.WithContent("withparam.md", ` 1587+++ 1588title = "With Param!" 1589[author] 1590 name = "Ernest Miller Hemingway" 1591 1592+++ 1593 1594`, 1595 1596 "noparam.md", ` 1597--- 1598title: "No Param!" 1599--- 1600`, "withstringparam.md", ` 1601+++ 1602title = "With string Param!" 1603author = "Jo Nesbø" 1604 1605+++ 1606 1607`) 1608 b.Build(BuildCfg{}) 1609 1610 b.AssertFileContent("public/index.html", 1611 "Author page: Ernest Miller Hemingway", 1612 "Author name page string: Kurt Vonnegut|", 1613 "Author page string: Jo Nesbø|", 1614 "Author site config: Kurt Vonnegut") 1615} 1616 1617func TestGoldmark(t *testing.T) { 1618 t.Parallel() 1619 1620 b := newTestSitesBuilder(t).WithConfigFile("toml", ` 1621baseURL = "https://example.org" 1622 1623[markup] 1624defaultMarkdownHandler="goldmark" 1625[markup.goldmark] 1626[markup.goldmark.renderer] 1627unsafe = false 1628[markup.highlight] 1629noClasses=false 1630 1631 1632`) 1633 b.WithTemplatesAdded("_default/single.html", ` 1634Title: {{ .Title }} 1635ToC: {{ .TableOfContents }} 1636Content: {{ .Content }} 1637 1638`, "shortcodes/t.html", `T-SHORT`, "shortcodes/s.html", `## Code 1639{{ .Inner }} 1640`) 1641 1642 content := ` 1643+++ 1644title = "A Page!" 1645+++ 1646 1647## Shortcode {{% t %}} in header 1648 1649## Code Fense in Shortcode 1650 1651{{% s %}} 1652$$$bash {hl_lines=[1]} 1653SHORT 1654$$$ 1655{{% /s %}} 1656 1657## Code Fence 1658 1659$$$bash {hl_lines=[1]} 1660MARKDOWN 1661$$$ 1662 1663Link with URL as text 1664 1665[https://google.com](https://google.com) 1666 1667 1668` 1669 content = strings.ReplaceAll(content, "$$$", "```") 1670 1671 b.WithContent("page.md", content) 1672 1673 b.Build(BuildCfg{}) 1674 1675 b.AssertFileContent("public/page/index.html", 1676 `<nav id="TableOfContents"> 1677<li><a href="#shortcode-t-short-in-header">Shortcode T-SHORT in header</a></li> 1678<code class="language-bash" data-lang="bash"><span class="hl">SHORT 1679<code class="language-bash" data-lang="bash"><span class="hl">MARKDOWN 1680<p><a href="https://google.com">https://google.com</a></p> 1681`) 1682} 1683 1684func TestBlackfridayDefault(t *testing.T) { 1685 t.Parallel() 1686 1687 b := newTestSitesBuilder(t).WithConfigFile("toml", ` 1688baseURL = "https://example.org" 1689 1690[markup] 1691defaultMarkdownHandler="blackfriday" 1692[markup.highlight] 1693noClasses=false 1694[markup.goldmark] 1695[markup.goldmark.renderer] 1696unsafe=true 1697 1698 1699`) 1700 // Use the new attribute syntax to make sure it's not Goldmark. 1701 b.WithTemplatesAdded("_default/single.html", ` 1702Title: {{ .Title }} 1703Content: {{ .Content }} 1704 1705`, "shortcodes/s.html", `## Code 1706{{ .Inner }} 1707`) 1708 1709 content := ` 1710+++ 1711title = "A Page!" 1712+++ 1713 1714 1715## Code Fense in Shortcode 1716 1717{{% s %}} 1718S: 1719{{% s %}} 1720$$$bash {hl_lines=[1]} 1721SHORT 1722$$$ 1723{{% /s %}} 1724{{% /s %}} 1725 1726## Code Fence 1727 1728$$$bash {hl_lines=[1]} 1729MARKDOWN 1730$$$ 1731 1732` 1733 content = strings.ReplaceAll(content, "$$$", "```") 1734 1735 for i, ext := range []string{"md", "html"} { 1736 b.WithContent(fmt.Sprintf("page%d.%s", i+1, ext), content) 1737 } 1738 1739 b.Build(BuildCfg{}) 1740 1741 // Blackfriday does not support this extended attribute syntax. 1742 b.AssertFileContent("public/page1/index.html", 1743 `<pre tabindex="0"><code class="language-bash {hl_lines=[1]}" data-lang="bash {hl_lines=[1]}">SHORT</code></pre>`, 1744 `<pre tabindex="0"><code class="language-bash {hl_lines=[1]}" data-lang="bash {hl_lines=[1]}">MARKDOWN`, 1745 ) 1746 1747 b.AssertFileContent("public/page2/index.html", 1748 `<pre tabindex="0"><code class="language-bash {hl_lines=[1]}" data-lang="bash {hl_lines=[1]}">SHORT`, 1749 ) 1750} 1751 1752func TestPageCaseIssues(t *testing.T) { 1753 t.Parallel() 1754 1755 b := newTestSitesBuilder(t) 1756 b.WithConfigFile("toml", `defaultContentLanguage = "no" 1757[languages] 1758[languages.NO] 1759title = "Norsk" 1760`) 1761 b.WithContent("a/B/C/Page1.md", "---\ntitle: Page1\n---") 1762 b.WithTemplates("index.html", ` 1763{{ $p1 := site.GetPage "a/B/C/Page1" }} 1764Lang: {{ .Lang }} 1765Page1: {{ $p1.Path }} 1766`) 1767 1768 b.Build(BuildCfg{}) 1769 1770 b.AssertFileContent("public/index.html", "Lang: no", filepath.FromSlash("Page1: a/B/C/Page1.md")) 1771} 1772