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