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