github.com/lyeb/hugo@v0.47.1/hugolib/page_test.go (about) 1 // Copyright 2018 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 "bytes" 18 "fmt" 19 "html/template" 20 "os" 21 22 "path/filepath" 23 "reflect" 24 "sort" 25 "strings" 26 "testing" 27 "time" 28 29 "github.com/gohugoio/hugo/hugofs" 30 "github.com/spf13/afero" 31 32 "github.com/spf13/viper" 33 34 "github.com/gohugoio/hugo/deps" 35 "github.com/gohugoio/hugo/helpers" 36 "github.com/spf13/cast" 37 "github.com/stretchr/testify/assert" 38 "github.com/stretchr/testify/require" 39 ) 40 41 var emptyPage = "" 42 43 const ( 44 homePage = "---\ntitle: Home\n---\nHome Page Content\n" 45 simplePage = "---\ntitle: Simple\n---\nSimple Page\n" 46 renderNoFrontmatter = "<!doctype><html><head></head><body>This is a test</body></html>" 47 contentNoFrontmatter = "Page without front matter.\n" 48 contentWithCommentedFrontmatter = "<!--\n+++\ntitle = \"Network configuration\"\ndescription = \"Docker networking\"\nkeywords = [\"network\"]\n[menu.main]\nparent= \"smn_administrate\"\n+++\n-->\n\n# Network configuration\n\n##\nSummary" 49 contentWithCommentedTextFrontmatter = "<!--[metaData]>\n+++\ntitle = \"Network configuration\"\ndescription = \"Docker networking\"\nkeywords = [\"network\"]\n[menu.main]\nparent= \"smn_administrate\"\n+++\n<![end-metadata]-->\n\n# Network configuration\n\n##\nSummary" 50 contentWithCommentedLongFrontmatter = "<!--[metaData123456789012345678901234567890]>\n+++\ntitle = \"Network configuration\"\ndescription = \"Docker networking\"\nkeywords = [\"network\"]\n[menu.main]\nparent= \"smn_administrate\"\n+++\n<![end-metadata]-->\n\n# Network configuration\n\n##\nSummary" 51 contentWithCommentedLong2Frontmatter = "<!--[metaData]>\n+++\ntitle = \"Network configuration\"\ndescription = \"Docker networking\"\nkeywords = [\"network\"]\n[menu.main]\nparent= \"smn_administrate\"\n+++\n<![end-metadata123456789012345678901234567890]-->\n\n# Network configuration\n\n##\nSummary" 52 invalidFrontmatterShortDelim = ` 53 -- 54 title: Short delim start 55 --- 56 Short Delim 57 ` 58 59 invalidFrontmatterShortDelimEnding = ` 60 --- 61 title: Short delim ending 62 -- 63 Short Delim 64 ` 65 66 invalidFrontmatterLadingWs = ` 67 68 --- 69 title: Leading WS 70 --- 71 Leading 72 ` 73 74 simplePageJSON = ` 75 { 76 "title": "spf13-vim 3.0 release and new website", 77 "description": "spf13-vim is a cross platform distribution of vim plugins and resources for Vim.", 78 "tags": [ ".vimrc", "plugins", "spf13-vim", "VIm" ], 79 "date": "2012-04-06", 80 "categories": [ 81 "Development", 82 "VIM" 83 ], 84 "slug": "-spf13-vim-3-0-release-and-new-website-" 85 } 86 87 Content of the file goes Here 88 ` 89 90 simplePageRFC3339Date = "---\ntitle: RFC3339 Date\ndate: \"2013-05-17T16:59:30Z\"\n---\nrfc3339 content" 91 simplePageJSONMultiple = ` 92 { 93 "title": "foobar", 94 "customData": { "foo": "bar" }, 95 "date": "2012-08-06" 96 } 97 Some text 98 ` 99 100 simplePageWithSummaryDelimiter = `--- 101 title: Simple 102 --- 103 Summary Next Line 104 105 <!--more--> 106 Some more text 107 ` 108 109 simplePageWithSummaryDelimiterAndMarkdownThatCrossesBorder = `--- 110 title: Simple 111 --- 112 The [best static site generator][hugo].[^1] 113 <!--more--> 114 [hugo]: http://gohugo.io/ 115 [^1]: Many people say so. 116 ` 117 simplePageWithShortcodeInSummary = `--- 118 title: Simple 119 --- 120 Summary Next Line. {{<figure src="/not/real" >}}. 121 More text here. 122 123 Some more text 124 ` 125 126 simplePageWithEmbeddedScript = `--- 127 title: Simple 128 --- 129 <script type='text/javascript'>alert('the script tags are still there, right?');</script> 130 ` 131 132 simplePageWithSummaryDelimiterSameLine = `--- 133 title: Simple 134 --- 135 Summary Same Line<!--more--> 136 137 Some more text 138 ` 139 140 simplePageWithSummaryDelimiterOnlySummary = `--- 141 title: Simple 142 --- 143 Summary text 144 145 <!--more--> 146 ` 147 148 simplePageWithAllCJKRunes = `--- 149 title: Simple 150 --- 151 152 153 € € € € € 154 你好 155 도형이 156 カテゴリー 157 158 159 ` 160 161 simplePageWithMainEnglishWithCJKRunes = `--- 162 title: Simple 163 --- 164 165 166 In Chinese, 好 means good. In Chinese, 好 means good. 167 In Chinese, 好 means good. In Chinese, 好 means good. 168 In Chinese, 好 means good. In Chinese, 好 means good. 169 In Chinese, 好 means good. In Chinese, 好 means good. 170 In Chinese, 好 means good. In Chinese, 好 means good. 171 In Chinese, 好 means good. In Chinese, 好 means good. 172 In Chinese, 好 means good. In Chinese, 好 means good. 173 More then 70 words. 174 175 176 ` 177 simplePageWithMainEnglishWithCJKRunesSummary = "In Chinese, 好 means good. In Chinese, 好 means good. " + 178 "In Chinese, 好 means good. In Chinese, 好 means good. " + 179 "In Chinese, 好 means good. In Chinese, 好 means good. " + 180 "In Chinese, 好 means good. In Chinese, 好 means good. " + 181 "In Chinese, 好 means good. In Chinese, 好 means good. " + 182 "In Chinese, 好 means good. In Chinese, 好 means good. " + 183 "In Chinese, 好 means good. In Chinese, 好 means good." 184 185 simplePageWithIsCJKLanguageFalse = `--- 186 title: Simple 187 isCJKLanguage: false 188 --- 189 190 In Chinese, 好的啊 means good. In Chinese, 好的呀 means good. 191 In Chinese, 好的啊 means good. In Chinese, 好的呀 means good. 192 In Chinese, 好的啊 means good. In Chinese, 好的呀 means good. 193 In Chinese, 好的啊 means good. In Chinese, 好的呀 means good. 194 In Chinese, 好的啊 means good. In Chinese, 好的呀 means good. 195 In Chinese, 好的啊 means good. In Chinese, 好的呀 means good. 196 In Chinese, 好的啊 means good. In Chinese, 好的呀呀 means good enough. 197 More then 70 words. 198 199 200 ` 201 simplePageWithIsCJKLanguageFalseSummary = "In Chinese, 好的啊 means good. In Chinese, 好的呀 means good. " + 202 "In Chinese, 好的啊 means good. In Chinese, 好的呀 means good. " + 203 "In Chinese, 好的啊 means good. In Chinese, 好的呀 means good. " + 204 "In Chinese, 好的啊 means good. In Chinese, 好的呀 means good. " + 205 "In Chinese, 好的啊 means good. In Chinese, 好的呀 means good. " + 206 "In Chinese, 好的啊 means good. In Chinese, 好的呀 means good. " + 207 "In Chinese, 好的啊 means good. In Chinese, 好的呀呀 means good enough." 208 209 simplePageWithLongContent = `--- 210 title: Simple 211 --- 212 213 Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor 214 incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis 215 nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. 216 Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu 217 fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in 218 culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit 219 amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore 220 et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation 221 ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor 222 in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla 223 pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui 224 officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, 225 consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et 226 dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco 227 laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in 228 reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. 229 Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia 230 deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur 231 adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna 232 aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi 233 ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in 234 voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint 235 occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim 236 id est laborum. Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed 237 do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim 238 veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo 239 consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse 240 cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non 241 proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem 242 ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor 243 incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis 244 nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. 245 Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu 246 fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in 247 culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit 248 amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore 249 et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation 250 ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor 251 in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla 252 pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui 253 officia deserunt mollit anim id est laborum.` 254 255 pageWithToC = `--- 256 title: TOC 257 --- 258 For some moments the old man did not reply. He stood with bowed head, buried in deep thought. But at last he spoke. 259 260 ## AA 261 262 I have no idea, of course, how long it took me to reach the limit of the plain, 263 but at last I entered the foothills, following a pretty little canyon upward 264 toward the mountains. Beside me frolicked a laughing brooklet, hurrying upon 265 its noisy way down to the silent sea. In its quieter pools I discovered many 266 small fish, of four-or five-pound weight I should imagine. In appearance, 267 except as to size and color, they were not unlike the whale of our own seas. As 268 I watched them playing about I discovered, not only that they suckled their 269 young, but that at intervals they rose to the surface to breathe as well as to 270 feed upon certain grasses and a strange, scarlet lichen which grew upon the 271 rocks just above the water line. 272 273 ### AAA 274 275 I remember I felt an extraordinary persuasion that I was being played with, 276 that presently, when I was upon the very verge of safety, this mysterious 277 death--as swift as the passage of light--would leap after me from the pit about 278 the cylinder and strike me down. ## BB 279 280 ### BBB 281 282 "You're a great Granser," he cried delightedly, "always making believe them little marks mean something." 283 ` 284 285 simplePageWithAdditionalExtension = `+++ 286 [blackfriday] 287 extensions = ["hardLineBreak"] 288 +++ 289 first line. 290 second line. 291 292 fourth line. 293 ` 294 295 simplePageWithURL = `--- 296 title: Simple 297 url: simple/url/ 298 --- 299 Simple Page With URL` 300 301 simplePageWithSlug = `--- 302 title: Simple 303 slug: simple-slug 304 --- 305 Simple Page With Slug` 306 307 simplePageWithDate = `--- 308 title: Simple 309 date: '2013-10-15T06:16:13' 310 --- 311 Simple Page With Date` 312 313 UTF8Page = `--- 314 title: ラーメン 315 --- 316 UTF8 Page` 317 318 UTF8PageWithURL = `--- 319 title: ラーメン 320 url: ラーメン/url/ 321 --- 322 UTF8 Page With URL` 323 324 UTF8PageWithSlug = `--- 325 title: ラーメン 326 slug: ラーメン-slug 327 --- 328 UTF8 Page With Slug` 329 330 UTF8PageWithDate = `--- 331 title: ラーメン 332 date: '2013-10-15T06:16:13' 333 --- 334 UTF8 Page With Date` 335 ) 336 337 var pageWithVariousFrontmatterTypes = `+++ 338 a_string = "bar" 339 an_integer = 1 340 a_float = 1.3 341 a_bool = false 342 a_date = 1979-05-27T07:32:00Z 343 344 [a_table] 345 a_key = "a_value" 346 +++ 347 Front Matter with various frontmatter types` 348 349 var pageWithCalendarYAMLFrontmatter = `--- 350 type: calendar 351 weeks: 352 - 353 start: "Jan 5" 354 days: 355 - activity: class 356 room: EN1000 357 - activity: lab 358 - activity: class 359 - activity: lab 360 - activity: class 361 - 362 start: "Jan 12" 363 days: 364 - activity: class 365 - activity: lab 366 - activity: class 367 - activity: lab 368 - activity: exam 369 --- 370 371 Hi. 372 ` 373 374 var pageWithCalendarJSONFrontmatter = `{ 375 "type": "calendar", 376 "weeks": [ 377 { 378 "start": "Jan 5", 379 "days": [ 380 { "activity": "class", "room": "EN1000" }, 381 { "activity": "lab" }, 382 { "activity": "class" }, 383 { "activity": "lab" }, 384 { "activity": "class" } 385 ] 386 }, 387 { 388 "start": "Jan 12", 389 "days": [ 390 { "activity": "class" }, 391 { "activity": "lab" }, 392 { "activity": "class" }, 393 { "activity": "lab" }, 394 { "activity": "exam" } 395 ] 396 } 397 ] 398 } 399 400 Hi. 401 ` 402 403 var pageWithCalendarTOMLFrontmatter = `+++ 404 type = "calendar" 405 406 [[weeks]] 407 start = "Jan 5" 408 409 [[weeks.days]] 410 activity = "class" 411 room = "EN1000" 412 413 [[weeks.days]] 414 activity = "lab" 415 416 [[weeks.days]] 417 activity = "class" 418 419 [[weeks.days]] 420 activity = "lab" 421 422 [[weeks.days]] 423 activity = "class" 424 425 [[weeks]] 426 start = "Jan 12" 427 428 [[weeks.days]] 429 activity = "class" 430 431 [[weeks.days]] 432 activity = "lab" 433 434 [[weeks.days]] 435 activity = "class" 436 437 [[weeks.days]] 438 activity = "lab" 439 440 [[weeks.days]] 441 activity = "exam" 442 +++ 443 444 Hi. 445 ` 446 447 func checkError(t *testing.T, err error, expected string) { 448 if err == nil { 449 t.Fatalf("err is nil. Expected: %s", expected) 450 } 451 if !strings.Contains(err.Error(), expected) { 452 t.Errorf("err.Error() returned: '%s'. Expected: '%s'", err.Error(), expected) 453 } 454 } 455 456 func TestDegenerateEmptyPageZeroLengthName(t *testing.T) { 457 t.Parallel() 458 s := newTestSite(t) 459 _, err := s.NewPage("") 460 if err == nil { 461 t.Fatalf("A zero length page name must return an error") 462 } 463 464 checkError(t, err, "Zero length page name") 465 } 466 467 func TestDegenerateEmptyPage(t *testing.T) { 468 t.Parallel() 469 s := newTestSite(t) 470 _, err := s.NewPageFrom(strings.NewReader(emptyPage), "test") 471 if err != nil { 472 t.Fatalf("Empty files should not trigger an error. Should be able to touch a file while watching without erroring out.") 473 } 474 } 475 476 func checkPageTitle(t *testing.T, page *Page, title string) { 477 if page.title != title { 478 t.Fatalf("Page title is: %s. Expected %s", page.title, title) 479 } 480 } 481 482 func checkPageContent(t *testing.T, page *Page, content string, msg ...interface{}) { 483 a := normalizeContent(content) 484 b := normalizeContent(string(page.content())) 485 if a != b { 486 t.Fatalf("Page content is:\n%q\nExpected:\n%q (%q)", b, a, msg) 487 } 488 } 489 490 func normalizeContent(c string) string { 491 norm := c 492 norm = strings.Replace(norm, "\n", " ", -1) 493 norm = strings.Replace(norm, " ", " ", -1) 494 norm = strings.Replace(norm, " ", " ", -1) 495 norm = strings.Replace(norm, " ", " ", -1) 496 norm = strings.Replace(norm, "p> ", "p>", -1) 497 norm = strings.Replace(norm, "> <", "> <", -1) 498 return strings.TrimSpace(norm) 499 } 500 501 func checkPageTOC(t *testing.T, page *Page, toc string) { 502 if page.TableOfContents != template.HTML(toc) { 503 t.Fatalf("Page TableOfContents is: %q.\nExpected %q", page.TableOfContents, toc) 504 } 505 } 506 507 func checkPageSummary(t *testing.T, page *Page, summary string, msg ...interface{}) { 508 a := normalizeContent(string(page.summary)) 509 b := normalizeContent(summary) 510 if a != b { 511 t.Fatalf("Page summary is:\n%q.\nExpected\n%q (%q)", a, b, msg) 512 } 513 } 514 515 func checkPageType(t *testing.T, page *Page, pageType string) { 516 if page.Type() != pageType { 517 t.Fatalf("Page type is: %s. Expected: %s", page.Type(), pageType) 518 } 519 } 520 521 func checkPageDate(t *testing.T, page *Page, time time.Time) { 522 if page.Date != time { 523 t.Fatalf("Page date is: %s. Expected: %s", page.Date, time) 524 } 525 } 526 527 func checkTruncation(t *testing.T, page *Page, shouldBe bool, msg string) { 528 if page.Summary() == "" { 529 t.Fatal("page has no summary, can not check truncation") 530 } 531 if page.truncated != shouldBe { 532 if shouldBe { 533 t.Fatalf("page wasn't truncated: %s", msg) 534 } else { 535 t.Fatalf("page was truncated: %s", msg) 536 } 537 } 538 } 539 540 func normalizeExpected(ext, str string) string { 541 str = normalizeContent(str) 542 switch ext { 543 default: 544 return str 545 case "html": 546 return strings.Trim(helpers.StripHTML(str), " ") 547 case "ad": 548 paragraphs := strings.Split(str, "</p>") 549 expected := "" 550 for _, para := range paragraphs { 551 if para == "" { 552 continue 553 } 554 expected += fmt.Sprintf("<div class=\"paragraph\">\n%s</p></div>\n", para) 555 } 556 return expected 557 case "rst": 558 return fmt.Sprintf("<div class=\"document\">\n\n\n%s</div>", str) 559 } 560 } 561 562 func testAllMarkdownEnginesForPages(t *testing.T, 563 assertFunc func(t *testing.T, ext string, pages Pages), settings map[string]interface{}, pageSources ...string) { 564 565 engines := []struct { 566 ext string 567 shouldExecute func() bool 568 }{ 569 {"md", func() bool { return true }}, 570 {"mmark", func() bool { return true }}, 571 {"ad", func() bool { return helpers.HasAsciidoc() }}, 572 // TODO(bep) figure a way to include this without too much work.{"html", func() bool { return true }}, 573 {"rst", func() bool { return helpers.HasRst() }}, 574 } 575 576 for _, e := range engines { 577 if !e.shouldExecute() { 578 continue 579 } 580 581 cfg, fs := newTestCfg() 582 583 if settings != nil { 584 for k, v := range settings { 585 cfg.Set(k, v) 586 } 587 } 588 589 contentDir := "content" 590 591 if s := cfg.GetString("contentDir"); s != "" { 592 contentDir = s 593 } 594 595 var fileSourcePairs []string 596 597 for i, source := range pageSources { 598 fileSourcePairs = append(fileSourcePairs, fmt.Sprintf("p%d.%s", i, e.ext), source) 599 } 600 601 for i := 0; i < len(fileSourcePairs); i += 2 { 602 writeSource(t, fs, filepath.Join(contentDir, fileSourcePairs[i]), fileSourcePairs[i+1]) 603 } 604 605 // Add a content page for the home page 606 homePath := fmt.Sprintf("_index.%s", e.ext) 607 writeSource(t, fs, filepath.Join(contentDir, homePath), homePage) 608 609 s := buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{SkipRender: true}) 610 611 require.Len(t, s.RegularPages, len(pageSources)) 612 613 assertFunc(t, e.ext, s.RegularPages) 614 615 home, err := s.Info.Home() 616 require.NoError(t, err) 617 require.NotNil(t, home) 618 require.Equal(t, homePath, home.Path()) 619 require.Contains(t, home.content(), "Home Page Content") 620 621 } 622 623 } 624 625 func TestCreateNewPage(t *testing.T) { 626 t.Parallel() 627 assertFunc := func(t *testing.T, ext string, pages Pages) { 628 p := pages[0] 629 630 // issue #2290: Path is relative to the content dir and will continue to be so. 631 require.Equal(t, filepath.FromSlash(fmt.Sprintf("p0.%s", ext)), p.Path()) 632 assert.False(t, p.IsHome()) 633 checkPageTitle(t, p, "Simple") 634 checkPageContent(t, p, normalizeExpected(ext, "<p>Simple Page</p>\n")) 635 checkPageSummary(t, p, "Simple Page") 636 checkPageType(t, p, "page") 637 checkTruncation(t, p, false, "simple short page") 638 } 639 640 settings := map[string]interface{}{ 641 "contentDir": "mycontent", 642 } 643 644 testAllMarkdownEnginesForPages(t, assertFunc, settings, simplePage) 645 } 646 647 func TestSplitSummaryAndContent(t *testing.T) { 648 t.Parallel() 649 for i, this := range []struct { 650 markup string 651 content string 652 expectedSummary string 653 expectedContent string 654 }{ 655 {"markdown", `<p>Summary Same LineHUGOMORE42</p> 656 657 <p>Some more text</p>`, "<p>Summary Same Line</p>", "<p>Summary Same Line</p>\n\n<p>Some more text</p>"}, 658 {"asciidoc", `<div class="paragraph"><p>sn</p></div><div class="paragraph"><p>HUGOMORE42Some more text</p></div>`, 659 "<div class=\"paragraph\"><p>sn</p></div>", 660 "<div class=\"paragraph\"><p>sn</p></div><div class=\"paragraph\"><p>Some more text</p></div>"}, 661 {"rst", 662 "<div class=\"document\"><p>Summary Next Line</p><p>HUGOMORE42Some more text</p></div>", 663 "<div class=\"document\"><p>Summary Next Line</p></div>", 664 "<div class=\"document\"><p>Summary Next Line</p><p>Some more text</p></div>"}, 665 {"markdown", "<p>a</p><p>b</p><p>HUGOMORE42c</p>", "<p>a</p><p>b</p>", "<p>a</p><p>b</p><p>c</p>"}, 666 {"markdown", "<p>a</p><p>b</p><p>cHUGOMORE42</p>", "<p>a</p><p>b</p><p>c</p>", "<p>a</p><p>b</p><p>c</p>"}, 667 {"markdown", "<p>a</p><p>bHUGOMORE42</p><p>c</p>", "<p>a</p><p>b</p>", "<p>a</p><p>b</p><p>c</p>"}, 668 {"markdown", "<p>aHUGOMORE42</p><p>b</p><p>c</p>", "<p>a</p>", "<p>a</p><p>b</p><p>c</p>"}, 669 {"markdown", " HUGOMORE42 ", "", ""}, 670 {"markdown", "HUGOMORE42", "", ""}, 671 {"markdown", "<p>HUGOMORE42", "<p>", "<p>"}, 672 {"markdown", "HUGOMORE42<p>", "", "<p>"}, 673 {"markdown", "\n\n<p>HUGOMORE42</p>\n", "<p></p>", "<p></p>"}, 674 // Issue #2586 675 // Note: Hugo will not split mid-sentence but will look for the closest 676 // paragraph end marker. This may be a change from Hugo 0.16, but it makes sense. 677 {"markdown", `<p>this is an example HUGOMORE42of the issue.</p>`, 678 "<p>this is an example of the issue.</p>", 679 "<p>this is an example of the issue.</p>"}, 680 // Issue: #2538 681 {"markdown", fmt.Sprintf(` <p class="lead">%s</p>HUGOMORE42<p>%s</p> 682 `, 683 strings.Repeat("A", 10), strings.Repeat("B", 31)), 684 fmt.Sprintf(`<p class="lead">%s</p>`, strings.Repeat("A", 10)), 685 fmt.Sprintf(`<p class="lead">%s</p><p>%s</p>`, strings.Repeat("A", 10), strings.Repeat("B", 31)), 686 }, 687 } { 688 689 sc, err := splitUserDefinedSummaryAndContent(this.markup, []byte(this.content)) 690 691 require.NoError(t, err) 692 require.NotNil(t, sc, fmt.Sprintf("[%d] Nil %s", i, this.markup)) 693 require.Equal(t, this.expectedSummary, string(sc.summary), fmt.Sprintf("[%d] Summary markup %s", i, this.markup)) 694 require.Equal(t, this.expectedContent, string(sc.content), fmt.Sprintf("[%d] Content markup %s", i, this.markup)) 695 } 696 } 697 698 func TestPageWithDelimiter(t *testing.T) { 699 t.Parallel() 700 assertFunc := func(t *testing.T, ext string, pages Pages) { 701 p := pages[0] 702 checkPageTitle(t, p, "Simple") 703 checkPageContent(t, p, normalizeExpected(ext, "<p>Summary Next Line</p>\n\n<p>Some more text</p>\n"), ext) 704 checkPageSummary(t, p, normalizeExpected(ext, "<p>Summary Next Line</p>"), ext) 705 checkPageType(t, p, "page") 706 checkTruncation(t, p, true, "page with summary delimiter") 707 } 708 709 testAllMarkdownEnginesForPages(t, assertFunc, nil, simplePageWithSummaryDelimiter) 710 } 711 712 // Issue #1076 713 func TestPageWithDelimiterForMarkdownThatCrossesBorder(t *testing.T) { 714 t.Parallel() 715 cfg, fs := newTestCfg() 716 717 writeSource(t, fs, filepath.Join("content", "simple.md"), simplePageWithSummaryDelimiterAndMarkdownThatCrossesBorder) 718 719 s := buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{SkipRender: true}) 720 721 require.Len(t, s.RegularPages, 1) 722 723 p := s.RegularPages[0] 724 725 if p.Summary() != template.HTML("<p>The <a href=\"http://gohugo.io/\">best static site generator</a>.<sup class=\"footnote-ref\" id=\"fnref:1\"><a href=\"#fn:1\">1</a></sup>\n</p>") { 726 t.Fatalf("Got summary:\n%q", p.Summary()) 727 } 728 729 if p.content() != template.HTML("<p>The <a href=\"http://gohugo.io/\">best static site generator</a>.<sup class=\"footnote-ref\" id=\"fnref:1\"><a href=\"#fn:1\">1</a></sup>\n</p>\n<div class=\"footnotes\">\n\n<hr />\n\n<ol>\n<li id=\"fn:1\">Many people say so.\n <a class=\"footnote-return\" href=\"#fnref:1\"><sup>[return]</sup></a></li>\n</ol>\n</div>") { 730 t.Fatalf("Got content:\n%q", p.content()) 731 } 732 } 733 734 // Issue #3854 735 // Also see https://github.com/gohugoio/hugo/issues/3977 736 func TestPageWithDateFields(t *testing.T) { 737 assert := require.New(t) 738 pageWithDate := `--- 739 title: P%d 740 weight: %d 741 %s: 2017-10-13 742 --- 743 Simple Page With Some Date` 744 745 hasDate := func(p *Page) bool { 746 return p.Date.Year() == 2017 747 } 748 749 datePage := func(field string, weight int) string { 750 return fmt.Sprintf(pageWithDate, weight, weight, field) 751 } 752 753 t.Parallel() 754 assertFunc := func(t *testing.T, ext string, pages Pages) { 755 assert.True(len(pages) > 0) 756 for _, p := range pages { 757 assert.True(hasDate(p)) 758 } 759 760 } 761 762 fields := []string{"date", "publishdate", "pubdate", "published"} 763 pageContents := make([]string, len(fields)) 764 for i, field := range fields { 765 pageContents[i] = datePage(field, i+1) 766 } 767 768 testAllMarkdownEnginesForPages(t, assertFunc, nil, pageContents...) 769 } 770 771 // Issue #2601 772 func TestPageRawContent(t *testing.T) { 773 t.Parallel() 774 cfg, fs := newTestCfg() 775 776 writeSource(t, fs, filepath.Join("content", "raw.md"), `--- 777 title: Raw 778 --- 779 **Raw**`) 780 781 writeSource(t, fs, filepath.Join("layouts", "_default", "single.html"), `{{ .RawContent }}`) 782 783 s := buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{SkipRender: true}) 784 785 require.Len(t, s.RegularPages, 1) 786 p := s.RegularPages[0] 787 788 require.Contains(t, p.RawContent(), "**Raw**") 789 790 } 791 792 func TestPageWithShortCodeInSummary(t *testing.T) { 793 t.Parallel() 794 assertFunc := func(t *testing.T, ext string, pages Pages) { 795 p := pages[0] 796 checkPageTitle(t, p, "Simple") 797 checkPageContent(t, p, normalizeExpected(ext, "<p>Summary Next Line. \n<figure>\n \n <img src=\"/not/real\" />\n \n \n</figure>\n.\nMore text here.</p>\n\n<p>Some more text</p>\n")) 798 checkPageSummary(t, p, "Summary Next Line. . More text here. Some more text") 799 checkPageType(t, p, "page") 800 } 801 802 testAllMarkdownEnginesForPages(t, assertFunc, nil, simplePageWithShortcodeInSummary) 803 } 804 805 func TestPageWithEmbeddedScriptTag(t *testing.T) { 806 t.Parallel() 807 assertFunc := func(t *testing.T, ext string, pages Pages) { 808 p := pages[0] 809 if ext == "ad" || ext == "rst" { 810 // TOD(bep) 811 return 812 } 813 checkPageContent(t, p, "<script type='text/javascript'>alert('the script tags are still there, right?');</script>\n", ext) 814 } 815 816 testAllMarkdownEnginesForPages(t, assertFunc, nil, simplePageWithEmbeddedScript) 817 } 818 819 func TestPageWithAdditionalExtension(t *testing.T) { 820 t.Parallel() 821 cfg, fs := newTestCfg() 822 823 writeSource(t, fs, filepath.Join("content", "simple.md"), simplePageWithAdditionalExtension) 824 825 s := buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{SkipRender: true}) 826 827 require.Len(t, s.RegularPages, 1) 828 829 p := s.RegularPages[0] 830 831 checkPageContent(t, p, "<p>first line.<br />\nsecond line.</p>\n\n<p>fourth line.</p>\n") 832 } 833 834 func TestTableOfContents(t *testing.T) { 835 836 cfg, fs := newTestCfg() 837 838 writeSource(t, fs, filepath.Join("content", "tocpage.md"), pageWithToC) 839 840 s := buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{SkipRender: true}) 841 842 require.Len(t, s.RegularPages, 1) 843 844 p := s.RegularPages[0] 845 846 checkPageContent(t, p, "\n\n<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>\n\n<h2 id=\"aa\">AA</h2>\n\n<p>I have no idea, of course, how long it took me to reach the limit of the plain,\nbut at last I entered the foothills, following a pretty little canyon upward\ntoward the mountains. Beside me frolicked a laughing brooklet, hurrying upon\nits noisy way down to the silent sea. In its quieter pools I discovered many\nsmall fish, of four-or five-pound weight I should imagine. In appearance,\nexcept as to size and color, they were not unlike the whale of our own seas. As\nI watched them playing about I discovered, not only that they suckled their\nyoung, but that at intervals they rose to the surface to breathe as well as to\nfeed upon certain grasses and a strange, scarlet lichen which grew upon the\nrocks just above the water line.</p>\n\n<h3 id=\"aaa\">AAA</h3>\n\n<p>I remember I felt an extraordinary persuasion that I was being played with,\nthat presently, when I was upon the very verge of safety, this mysterious\ndeath–as swift as the passage of light–would leap after me from the pit about\nthe cylinder and strike me down. ## BB</p>\n\n<h3 id=\"bbb\">BBB</h3>\n\n<p>“You’re a great Granser,” he cried delightedly, “always making believe them little marks mean something.”</p>\n") 847 checkPageTOC(t, p, "<nav id=\"TableOfContents\">\n<ul>\n<li>\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></li>\n</ul></li>\n</ul>\n</nav>") 848 } 849 850 func TestPageWithMoreTag(t *testing.T) { 851 t.Parallel() 852 assertFunc := func(t *testing.T, ext string, pages Pages) { 853 p := pages[0] 854 checkPageTitle(t, p, "Simple") 855 checkPageContent(t, p, normalizeExpected(ext, "<p>Summary Same Line</p>\n\n<p>Some more text</p>\n")) 856 checkPageSummary(t, p, normalizeExpected(ext, "<p>Summary Same Line</p>")) 857 checkPageType(t, p, "page") 858 859 } 860 861 testAllMarkdownEnginesForPages(t, assertFunc, nil, simplePageWithSummaryDelimiterSameLine) 862 } 863 864 func TestPageWithMoreTagOnlySummary(t *testing.T) { 865 866 assertFunc := func(t *testing.T, ext string, pages Pages) { 867 p := pages[0] 868 checkTruncation(t, p, false, "page with summary delimiter at end") 869 } 870 871 testAllMarkdownEnginesForPages(t, assertFunc, nil, simplePageWithSummaryDelimiterOnlySummary) 872 } 873 874 // #2973 875 func TestSummaryWithHTMLTagsOnNextLine(t *testing.T) { 876 877 assertFunc := func(t *testing.T, ext string, pages Pages) { 878 p := pages[0] 879 require.Contains(t, p.Summary(), "Happy new year everyone!") 880 require.NotContains(t, p.Summary(), "User interface") 881 } 882 883 testAllMarkdownEnginesForPages(t, assertFunc, nil, `--- 884 title: Simple 885 --- 886 Happy new year everyone! 887 888 Here is the last report for commits in the year 2016. It covers hrev50718-hrev50829. 889 890 <!--more--> 891 892 <h3>User interface</h3> 893 894 `) 895 } 896 897 func TestPageWithDate(t *testing.T) { 898 t.Parallel() 899 cfg, fs := newTestCfg() 900 901 writeSource(t, fs, filepath.Join("content", "simple.md"), simplePageRFC3339Date) 902 903 s := buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{SkipRender: true}) 904 905 require.Len(t, s.RegularPages, 1) 906 907 p := s.RegularPages[0] 908 d, _ := time.Parse(time.RFC3339, "2013-05-17T16:59:30Z") 909 910 checkPageDate(t, p, d) 911 } 912 913 func TestPageWithLastmodFromGitInfo(t *testing.T) { 914 assrt := require.New(t) 915 916 // We need to use the OS fs for this. 917 cfg := viper.New() 918 fs := hugofs.NewFrom(hugofs.Os, cfg) 919 fs.Destination = &afero.MemMapFs{} 920 921 cfg.Set("frontmatter", map[string]interface{}{ 922 "lastmod": []string{":git", "lastmod"}, 923 }) 924 cfg.Set("defaultContentLanguage", "en") 925 926 langConfig := map[string]interface{}{ 927 "en": map[string]interface{}{ 928 "weight": 1, 929 "languageName": "English", 930 "contentDir": "content", 931 }, 932 "nn": map[string]interface{}{ 933 "weight": 2, 934 "languageName": "Nynorsk", 935 "contentDir": "content_nn", 936 }, 937 } 938 939 cfg.Set("languages", langConfig) 940 cfg.Set("enableGitInfo", true) 941 942 assrt.NoError(loadDefaultSettingsFor(cfg)) 943 assrt.NoError(loadLanguageSettings(cfg, nil)) 944 945 wd, err := os.Getwd() 946 assrt.NoError(err) 947 cfg.Set("workingDir", filepath.Join(wd, "testsite")) 948 949 h, err := NewHugoSites(deps.DepsCfg{Fs: fs, Cfg: cfg}) 950 951 assrt.NoError(err) 952 assrt.Len(h.Sites, 2) 953 954 require.NoError(t, h.Build(BuildCfg{SkipRender: true})) 955 956 enSite := h.Sites[0] 957 assrt.Len(enSite.RegularPages, 1) 958 959 // 2018-03-11 is the Git author date for testsite/content/first-post.md 960 assrt.Equal("2018-03-11", enSite.RegularPages[0].Lastmod.Format("2006-01-02")) 961 962 nnSite := h.Sites[1] 963 assrt.Len(nnSite.RegularPages, 1) 964 965 // 2018-08-11 is the Git author date for testsite/content_nn/first-post.md 966 assrt.Equal("2018-08-11", nnSite.RegularPages[0].Lastmod.Format("2006-01-02")) 967 968 } 969 970 func TestPageWithFrontMatterConfig(t *testing.T) { 971 t.Parallel() 972 973 for _, dateHandler := range []string{":filename", ":fileModTime"} { 974 t.Run(fmt.Sprintf("dateHandler=%q", dateHandler), func(t *testing.T) { 975 assrt := require.New(t) 976 cfg, fs := newTestCfg() 977 978 pageTemplate := ` 979 --- 980 title: Page 981 weight: %d 982 lastMod: 2018-02-28 983 %s 984 --- 985 Content 986 ` 987 988 cfg.Set("frontmatter", map[string]interface{}{ 989 "date": []string{dateHandler, "date"}, 990 }) 991 992 c1 := filepath.Join("content", "section", "2012-02-21-noslug.md") 993 c2 := filepath.Join("content", "section", "2012-02-22-slug.md") 994 995 writeSource(t, fs, c1, fmt.Sprintf(pageTemplate, 1, "")) 996 writeSource(t, fs, c2, fmt.Sprintf(pageTemplate, 2, "slug: aslug")) 997 998 c1fi, err := fs.Source.Stat(c1) 999 assrt.NoError(err) 1000 c2fi, err := fs.Source.Stat(c2) 1001 assrt.NoError(err) 1002 1003 s := buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{SkipRender: true}) 1004 1005 assrt.Len(s.RegularPages, 2) 1006 1007 noSlug := s.RegularPages[0] 1008 slug := s.RegularPages[1] 1009 1010 assrt.Equal(28, noSlug.Lastmod.Day()) 1011 1012 switch strings.ToLower(dateHandler) { 1013 case ":filename": 1014 assrt.False(noSlug.Date.IsZero()) 1015 assrt.False(slug.Date.IsZero()) 1016 assrt.Equal(2012, noSlug.Date.Year()) 1017 assrt.Equal(2012, slug.Date.Year()) 1018 assrt.Equal("noslug", noSlug.Slug) 1019 assrt.Equal("aslug", slug.Slug) 1020 case ":filemodtime": 1021 assrt.Equal(c1fi.ModTime().Year(), noSlug.Date.Year()) 1022 assrt.Equal(c2fi.ModTime().Year(), slug.Date.Year()) 1023 fallthrough 1024 default: 1025 assrt.Equal("", noSlug.Slug) 1026 assrt.Equal("aslug", slug.Slug) 1027 1028 } 1029 }) 1030 } 1031 1032 } 1033 1034 func TestWordCountWithAllCJKRunesWithoutHasCJKLanguage(t *testing.T) { 1035 t.Parallel() 1036 assertFunc := func(t *testing.T, ext string, pages Pages) { 1037 p := pages[0] 1038 if p.WordCount() != 8 { 1039 t.Fatalf("[%s] incorrect word count for content '%s'. expected %v, got %v", ext, p.plain, 8, p.WordCount()) 1040 } 1041 } 1042 1043 testAllMarkdownEnginesForPages(t, assertFunc, nil, simplePageWithAllCJKRunes) 1044 } 1045 1046 func TestWordCountWithAllCJKRunesHasCJKLanguage(t *testing.T) { 1047 t.Parallel() 1048 settings := map[string]interface{}{"hasCJKLanguage": true} 1049 1050 assertFunc := func(t *testing.T, ext string, pages Pages) { 1051 p := pages[0] 1052 if p.WordCount() != 15 { 1053 t.Fatalf("[%s] incorrect word count for content '%s'. expected %v, got %v", ext, p.plain, 15, p.WordCount()) 1054 } 1055 } 1056 testAllMarkdownEnginesForPages(t, assertFunc, settings, simplePageWithAllCJKRunes) 1057 } 1058 1059 func TestWordCountWithMainEnglishWithCJKRunes(t *testing.T) { 1060 t.Parallel() 1061 settings := map[string]interface{}{"hasCJKLanguage": true} 1062 1063 assertFunc := func(t *testing.T, ext string, pages Pages) { 1064 p := pages[0] 1065 if p.WordCount() != 74 { 1066 t.Fatalf("[%s] incorrect word count for content '%s'. expected %v, got %v", ext, p.plain, 74, p.WordCount()) 1067 } 1068 1069 if p.summary != simplePageWithMainEnglishWithCJKRunesSummary { 1070 t.Fatalf("[%s] incorrect Summary for content '%s'. expected %v, got %v", ext, p.plain, 1071 simplePageWithMainEnglishWithCJKRunesSummary, p.summary) 1072 } 1073 } 1074 1075 testAllMarkdownEnginesForPages(t, assertFunc, settings, simplePageWithMainEnglishWithCJKRunes) 1076 } 1077 1078 func TestWordCountWithIsCJKLanguageFalse(t *testing.T) { 1079 t.Parallel() 1080 settings := map[string]interface{}{ 1081 "hasCJKLanguage": true, 1082 } 1083 1084 assertFunc := func(t *testing.T, ext string, pages Pages) { 1085 p := pages[0] 1086 if p.WordCount() != 75 { 1087 t.Fatalf("[%s] incorrect word count for content '%s'. expected %v, got %v", ext, p.plain, 74, p.WordCount()) 1088 } 1089 1090 if p.summary != simplePageWithIsCJKLanguageFalseSummary { 1091 t.Fatalf("[%s] incorrect Summary for content '%s'. expected %v, got %v", ext, p.plain, 1092 simplePageWithIsCJKLanguageFalseSummary, p.summary) 1093 } 1094 } 1095 1096 testAllMarkdownEnginesForPages(t, assertFunc, settings, simplePageWithIsCJKLanguageFalse) 1097 1098 } 1099 1100 func TestWordCount(t *testing.T) { 1101 t.Parallel() 1102 assertFunc := func(t *testing.T, ext string, pages Pages) { 1103 p := pages[0] 1104 if p.WordCount() != 483 { 1105 t.Fatalf("[%s] incorrect word count. expected %v, got %v", ext, 483, p.WordCount()) 1106 } 1107 1108 if p.FuzzyWordCount() != 500 { 1109 t.Fatalf("[%s] incorrect word count. expected %v, got %v", ext, 500, p.WordCount()) 1110 } 1111 1112 if p.ReadingTime() != 3 { 1113 t.Fatalf("[%s] incorrect min read. expected %v, got %v", ext, 3, p.ReadingTime()) 1114 } 1115 1116 checkTruncation(t, p, true, "long page") 1117 } 1118 1119 testAllMarkdownEnginesForPages(t, assertFunc, nil, simplePageWithLongContent) 1120 } 1121 1122 func TestCreatePage(t *testing.T) { 1123 t.Parallel() 1124 var tests = []struct { 1125 r string 1126 }{ 1127 {simplePageJSON}, 1128 {simplePageJSONMultiple}, 1129 //{strings.NewReader(SIMPLE_PAGE_JSON_COMPACT)}, 1130 } 1131 1132 for i, test := range tests { 1133 s := newTestSite(t) 1134 p, _ := s.NewPage("page") 1135 if _, err := p.ReadFrom(strings.NewReader(test.r)); err != nil { 1136 t.Fatalf("[%d] Unable to parse page: %s", i, err) 1137 } 1138 } 1139 } 1140 1141 func TestDegenerateInvalidFrontMatterShortDelim(t *testing.T) { 1142 t.Parallel() 1143 var tests = []struct { 1144 r string 1145 err string 1146 }{ 1147 {invalidFrontmatterShortDelimEnding, "unable to read frontmatter at filepos 45: EOF"}, 1148 } 1149 for _, test := range tests { 1150 s := newTestSite(t) 1151 p, _ := s.NewPage("invalid/front/matter/short/delim") 1152 _, err := p.ReadFrom(strings.NewReader(test.r)) 1153 checkError(t, err, test.err) 1154 } 1155 } 1156 1157 func TestShouldRenderContent(t *testing.T) { 1158 t.Parallel() 1159 var tests = []struct { 1160 text string 1161 render bool 1162 }{ 1163 {contentNoFrontmatter, true}, 1164 // TODO how to deal with malformed frontmatter. In this case it'll be rendered as markdown. 1165 {invalidFrontmatterShortDelim, true}, 1166 {renderNoFrontmatter, false}, 1167 {contentWithCommentedFrontmatter, true}, 1168 {contentWithCommentedTextFrontmatter, true}, 1169 {contentWithCommentedLongFrontmatter, false}, 1170 {contentWithCommentedLong2Frontmatter, true}, 1171 } 1172 1173 for _, test := range tests { 1174 s := newTestSite(t) 1175 p, _ := s.NewPage("render/front/matter") 1176 _, err := p.ReadFrom(strings.NewReader(test.text)) 1177 p = pageMust(p, err) 1178 if p.IsRenderable() != test.render { 1179 t.Errorf("expected p.IsRenderable() == %t, got %t", test.render, p.IsRenderable()) 1180 } 1181 } 1182 } 1183 1184 // Issue #768 1185 func TestCalendarParamsVariants(t *testing.T) { 1186 t.Parallel() 1187 s := newTestSite(t) 1188 pageJSON, _ := s.NewPage("test/fileJSON.md") 1189 _, _ = pageJSON.ReadFrom(strings.NewReader(pageWithCalendarJSONFrontmatter)) 1190 1191 pageYAML, _ := s.NewPage("test/fileYAML.md") 1192 _, _ = pageYAML.ReadFrom(strings.NewReader(pageWithCalendarYAMLFrontmatter)) 1193 1194 pageTOML, _ := s.NewPage("test/fileTOML.md") 1195 _, _ = pageTOML.ReadFrom(strings.NewReader(pageWithCalendarTOMLFrontmatter)) 1196 1197 assert.True(t, compareObjects(pageJSON.params, pageYAML.params)) 1198 assert.True(t, compareObjects(pageJSON.params, pageTOML.params)) 1199 1200 } 1201 1202 func TestDifferentFrontMatterVarTypes(t *testing.T) { 1203 t.Parallel() 1204 s := newTestSite(t) 1205 page, _ := s.NewPage("test/file1.md") 1206 _, _ = page.ReadFrom(strings.NewReader(pageWithVariousFrontmatterTypes)) 1207 1208 dateval, _ := time.Parse(time.RFC3339, "1979-05-27T07:32:00Z") 1209 if page.getParamToLower("a_string") != "bar" { 1210 t.Errorf("frontmatter not handling strings correctly should be %s, got: %s", "bar", page.getParamToLower("a_string")) 1211 } 1212 if page.getParamToLower("an_integer") != 1 { 1213 t.Errorf("frontmatter not handling ints correctly should be %s, got: %s", "1", page.getParamToLower("an_integer")) 1214 } 1215 if page.getParamToLower("a_float") != 1.3 { 1216 t.Errorf("frontmatter not handling floats correctly should be %f, got: %s", 1.3, page.getParamToLower("a_float")) 1217 } 1218 if page.getParamToLower("a_bool") != false { 1219 t.Errorf("frontmatter not handling bools correctly should be %t, got: %s", false, page.getParamToLower("a_bool")) 1220 } 1221 if page.getParamToLower("a_date") != dateval { 1222 t.Errorf("frontmatter not handling dates correctly should be %s, got: %s", dateval, page.getParamToLower("a_date")) 1223 } 1224 param := page.getParamToLower("a_table") 1225 if param == nil { 1226 t.Errorf("frontmatter not handling tables correctly should be type of %v, got: type of %v", reflect.TypeOf(page.params["a_table"]), reflect.TypeOf(param)) 1227 } 1228 if cast.ToStringMap(param)["a_key"] != "a_value" { 1229 t.Errorf("frontmatter not handling values inside a table correctly should be %s, got: %s", "a_value", cast.ToStringMap(page.params["a_table"])["a_key"]) 1230 } 1231 } 1232 1233 func TestDegenerateInvalidFrontMatterLeadingWhitespace(t *testing.T) { 1234 t.Parallel() 1235 s := newTestSite(t) 1236 p, _ := s.NewPage("invalid/front/matter/leading/ws") 1237 _, err := p.ReadFrom(strings.NewReader(invalidFrontmatterLadingWs)) 1238 if err != nil { 1239 t.Fatalf("Unable to parse front matter given leading whitespace: %s", err) 1240 } 1241 } 1242 1243 func TestSectionEvaluation(t *testing.T) { 1244 t.Parallel() 1245 s := newTestSite(t) 1246 page, _ := s.NewPage(filepath.FromSlash("blue/file1.md")) 1247 page.ReadFrom(strings.NewReader(simplePage)) 1248 if page.Section() != "blue" { 1249 t.Errorf("Section should be %s, got: %s", "blue", page.Section()) 1250 } 1251 } 1252 1253 func TestSliceToLower(t *testing.T) { 1254 t.Parallel() 1255 tests := []struct { 1256 value []string 1257 expected []string 1258 }{ 1259 {[]string{"a", "b", "c"}, []string{"a", "b", "c"}}, 1260 {[]string{"a", "B", "c"}, []string{"a", "b", "c"}}, 1261 {[]string{"A", "B", "C"}, []string{"a", "b", "c"}}, 1262 } 1263 1264 for _, test := range tests { 1265 res := helpers.SliceToLower(test.value) 1266 for i, val := range res { 1267 if val != test.expected[i] { 1268 t.Errorf("Case mismatch. Expected %s, got %s", test.expected[i], res[i]) 1269 } 1270 } 1271 } 1272 } 1273 1274 func TestReplaceDivider(t *testing.T) { 1275 t.Parallel() 1276 1277 tests := []struct { 1278 content string 1279 from string 1280 to string 1281 expectedContent string 1282 expectedTruncated bool 1283 }{ 1284 {"none", "a", "b", "none", false}, 1285 {"summary <!--more--> content", "<!--more-->", "HUGO", "summary HUGO content", true}, 1286 {"summary\n\ndivider", "divider", "HUGO", "summary\n\nHUGO", false}, 1287 {"summary\n\ndivider\n\r", "divider", "HUGO", "summary\n\nHUGO\n\r", false}, 1288 } 1289 1290 for i, test := range tests { 1291 replaced, truncated := replaceDivider([]byte(test.content), []byte(test.from), []byte(test.to)) 1292 1293 if truncated != test.expectedTruncated { 1294 t.Fatalf("[%d] Expected truncated to be %t, was %t", i, test.expectedTruncated, truncated) 1295 } 1296 1297 if string(replaced) != test.expectedContent { 1298 t.Fatalf("[%d] Expected content to be %q, was %q", i, test.expectedContent, replaced) 1299 } 1300 } 1301 } 1302 1303 func BenchmarkReplaceDivider(b *testing.B) { 1304 divider := "HUGO_DIVIDER" 1305 from, to := []byte(divider), []byte("HUGO_REPLACED") 1306 1307 withDivider := make([][]byte, b.N) 1308 noDivider := make([][]byte, b.N) 1309 1310 for i := 0; i < b.N; i++ { 1311 withDivider[i] = []byte(strings.Repeat("Summary ", 5) + "\n" + divider + "\n" + strings.Repeat("Word ", 300)) 1312 noDivider[i] = []byte(strings.Repeat("Word ", 300)) 1313 } 1314 1315 b.ResetTimer() 1316 for i := 0; i < b.N; i++ { 1317 _, t1 := replaceDivider(withDivider[i], from, to) 1318 _, t2 := replaceDivider(noDivider[i], from, to) 1319 if !t1 { 1320 b.Fatal("Should be truncated") 1321 } 1322 if t2 { 1323 b.Fatal("Should not be truncated") 1324 } 1325 } 1326 } 1327 1328 func TestPagePaths(t *testing.T) { 1329 t.Parallel() 1330 1331 siteParmalinksSetting := map[string]string{ 1332 "post": ":year/:month/:day/:title/", 1333 } 1334 1335 tests := []struct { 1336 content string 1337 path string 1338 hasPermalink bool 1339 expected string 1340 }{ 1341 {simplePage, "post/x.md", false, "post/x.html"}, 1342 {simplePageWithURL, "post/x.md", false, "simple/url/index.html"}, 1343 {simplePageWithSlug, "post/x.md", false, "post/simple-slug.html"}, 1344 {simplePageWithDate, "post/x.md", true, "2013/10/15/simple/index.html"}, 1345 {UTF8Page, "post/x.md", false, "post/x.html"}, 1346 {UTF8PageWithURL, "post/x.md", false, "ラーメン/url/index.html"}, 1347 {UTF8PageWithSlug, "post/x.md", false, "post/ラーメン-slug.html"}, 1348 {UTF8PageWithDate, "post/x.md", true, "2013/10/15/ラーメン/index.html"}, 1349 } 1350 1351 for _, test := range tests { 1352 cfg, fs := newTestCfg() 1353 1354 if test.hasPermalink { 1355 cfg.Set("permalinks", siteParmalinksSetting) 1356 } 1357 1358 writeSource(t, fs, filepath.Join("content", filepath.FromSlash(test.path)), test.content) 1359 1360 s := buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{SkipRender: true}) 1361 require.Len(t, s.RegularPages, 1) 1362 1363 } 1364 } 1365 1366 var pageWithDraftAndPublished = `--- 1367 title: broken 1368 published: false 1369 draft: true 1370 --- 1371 some content 1372 ` 1373 1374 func TestDraftAndPublishedFrontMatterError(t *testing.T) { 1375 t.Parallel() 1376 s := newTestSite(t) 1377 _, err := s.NewPageFrom(strings.NewReader(pageWithDraftAndPublished), "content/post/broken.md") 1378 if err != ErrHasDraftAndPublished { 1379 t.Errorf("expected ErrHasDraftAndPublished, was %#v", err) 1380 } 1381 } 1382 1383 var pagesWithPublishedFalse = `--- 1384 title: okay 1385 published: false 1386 --- 1387 some content 1388 ` 1389 var pageWithPublishedTrue = `--- 1390 title: okay 1391 published: true 1392 --- 1393 some content 1394 ` 1395 1396 func TestPublishedFrontMatter(t *testing.T) { 1397 t.Parallel() 1398 s := newTestSite(t) 1399 p, err := s.NewPageFrom(strings.NewReader(pagesWithPublishedFalse), "content/post/broken.md") 1400 if err != nil { 1401 t.Fatalf("err during parse: %s", err) 1402 } 1403 if !p.Draft { 1404 t.Errorf("expected true, got %t", p.Draft) 1405 } 1406 p, err = s.NewPageFrom(strings.NewReader(pageWithPublishedTrue), "content/post/broken.md") 1407 if err != nil { 1408 t.Fatalf("err during parse: %s", err) 1409 } 1410 if p.Draft { 1411 t.Errorf("expected false, got %t", p.Draft) 1412 } 1413 } 1414 1415 var pagesDraftTemplate = []string{`--- 1416 title: "okay" 1417 draft: %t 1418 --- 1419 some content 1420 `, 1421 `+++ 1422 title = "okay" 1423 draft = %t 1424 +++ 1425 1426 some content 1427 `, 1428 } 1429 1430 func TestDraft(t *testing.T) { 1431 t.Parallel() 1432 s := newTestSite(t) 1433 for _, draft := range []bool{true, false} { 1434 for i, templ := range pagesDraftTemplate { 1435 pageContent := fmt.Sprintf(templ, draft) 1436 p, err := s.NewPageFrom(strings.NewReader(pageContent), "content/post/broken.md") 1437 if err != nil { 1438 t.Fatalf("err during parse: %s", err) 1439 } 1440 if p.Draft != draft { 1441 t.Errorf("[%d] expected %t, got %t", i, draft, p.Draft) 1442 } 1443 } 1444 } 1445 } 1446 1447 var pagesParamsTemplate = []string{`+++ 1448 title = "okay" 1449 draft = false 1450 tags = [ "hugo", "web" ] 1451 social= [ 1452 [ "a", "#" ], 1453 [ "b", "#" ], 1454 ] 1455 +++ 1456 some content 1457 `, 1458 `--- 1459 title: "okay" 1460 draft: false 1461 tags: 1462 - hugo 1463 - web 1464 social: 1465 - - a 1466 - "#" 1467 - - b 1468 - "#" 1469 --- 1470 some content 1471 `, 1472 `{ 1473 "title": "okay", 1474 "draft": false, 1475 "tags": [ "hugo", "web" ], 1476 "social": [ 1477 [ "a", "#" ], 1478 [ "b", "#" ] 1479 ] 1480 } 1481 some content 1482 `, 1483 } 1484 1485 func TestPageParams(t *testing.T) { 1486 t.Parallel() 1487 s := newTestSite(t) 1488 wantedMap := map[string]interface{}{ 1489 "tags": []string{"hugo", "web"}, 1490 // Issue #2752 1491 "social": []interface{}{ 1492 []interface{}{"a", "#"}, 1493 []interface{}{"b", "#"}, 1494 }, 1495 } 1496 1497 for i, c := range pagesParamsTemplate { 1498 p, err := s.NewPageFrom(strings.NewReader(c), "content/post/params.md") 1499 require.NoError(t, err, "err during parse", "#%d", i) 1500 for key := range wantedMap { 1501 assert.Equal(t, wantedMap[key], p.params[key], "#%d", key) 1502 } 1503 } 1504 } 1505 1506 func TestTraverse(t *testing.T) { 1507 exampleParams := `--- 1508 rating: "5 stars" 1509 tags: 1510 - hugo 1511 - web 1512 social: 1513 twitter: "@jxxf" 1514 facebook: "https://example.com" 1515 ---` 1516 t.Parallel() 1517 s := newTestSite(t) 1518 p, _ := s.NewPageFrom(strings.NewReader(exampleParams), "content/post/params.md") 1519 1520 topLevelKeyValue, _ := p.Param("rating") 1521 assert.Equal(t, "5 stars", topLevelKeyValue) 1522 1523 nestedStringKeyValue, _ := p.Param("social.twitter") 1524 assert.Equal(t, "@jxxf", nestedStringKeyValue) 1525 1526 nonexistentKeyValue, _ := p.Param("doesn't.exist") 1527 assert.Nil(t, nonexistentKeyValue) 1528 } 1529 1530 func TestPageSimpleMethods(t *testing.T) { 1531 t.Parallel() 1532 s := newTestSite(t) 1533 for i, this := range []struct { 1534 assertFunc func(p *Page) bool 1535 }{ 1536 {func(p *Page) bool { return !p.IsNode() }}, 1537 {func(p *Page) bool { return p.IsPage() }}, 1538 {func(p *Page) bool { return p.Plain() == "Do Be Do Be Do" }}, 1539 {func(p *Page) bool { return strings.Join(p.PlainWords(), " ") == "Do Be Do Be Do" }}, 1540 } { 1541 1542 p, _ := s.NewPage("Test") 1543 p.workContent = []byte("<h1>Do Be Do Be Do</h1>") 1544 p.resetContent() 1545 if !this.assertFunc(p) { 1546 t.Errorf("[%d] Page method error", i) 1547 } 1548 } 1549 } 1550 1551 func TestIndexPageSimpleMethods(t *testing.T) { 1552 s := newTestSite(t) 1553 t.Parallel() 1554 for i, this := range []struct { 1555 assertFunc func(n *Page) bool 1556 }{ 1557 {func(n *Page) bool { return n.IsNode() }}, 1558 {func(n *Page) bool { return !n.IsPage() }}, 1559 {func(n *Page) bool { return n.Scratch() != nil }}, 1560 {func(n *Page) bool { return n.Hugo() != nil }}, 1561 } { 1562 1563 n := s.newHomePage() 1564 1565 if !this.assertFunc(n) { 1566 t.Errorf("[%d] Node method error", i) 1567 } 1568 } 1569 } 1570 1571 func TestKind(t *testing.T) { 1572 t.Parallel() 1573 // Add tests for these constants to make sure they don't change 1574 require.Equal(t, "page", KindPage) 1575 require.Equal(t, "home", KindHome) 1576 require.Equal(t, "section", KindSection) 1577 require.Equal(t, "taxonomy", KindTaxonomy) 1578 require.Equal(t, "taxonomyTerm", KindTaxonomyTerm) 1579 1580 } 1581 1582 func TestTranslationKey(t *testing.T) { 1583 t.Parallel() 1584 assert := require.New(t) 1585 cfg, fs := newTestCfg() 1586 1587 writeSource(t, fs, filepath.Join("content", filepath.FromSlash("sect/simple.no.md")), "---\ntitle: \"A1\"\ntranslationKey: \"k1\"\n---\nContent\n") 1588 writeSource(t, fs, filepath.Join("content", filepath.FromSlash("sect/simple.en.md")), "---\ntitle: \"A2\"\n---\nContent\n") 1589 1590 s := buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{SkipRender: true}) 1591 1592 require.Len(t, s.RegularPages, 2) 1593 1594 home, _ := s.Info.Home() 1595 assert.NotNil(home) 1596 assert.Equal("home", home.TranslationKey()) 1597 assert.Equal("page/k1", s.RegularPages[0].TranslationKey()) 1598 p2 := s.RegularPages[1] 1599 1600 assert.Equal("page/sect/simple", p2.TranslationKey()) 1601 1602 } 1603 1604 func TestChompBOM(t *testing.T) { 1605 t.Parallel() 1606 const utf8BOM = "\xef\xbb\xbf" 1607 1608 cfg, fs := newTestCfg() 1609 1610 writeSource(t, fs, filepath.Join("content", "simple.md"), utf8BOM+simplePage) 1611 1612 s := buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{SkipRender: true}) 1613 1614 require.Len(t, s.RegularPages, 1) 1615 1616 p := s.RegularPages[0] 1617 1618 checkPageTitle(t, p, "Simple") 1619 } 1620 1621 // TODO(bep) this may be useful for other tests. 1622 func compareObjects(a interface{}, b interface{}) bool { 1623 aStr := strings.Split(fmt.Sprintf("%v", a), "") 1624 sort.Strings(aStr) 1625 1626 bStr := strings.Split(fmt.Sprintf("%v", b), "") 1627 sort.Strings(bStr) 1628 1629 return strings.Join(aStr, "") == strings.Join(bStr, "") 1630 } 1631 1632 func TestShouldBuild(t *testing.T) { 1633 t.Parallel() 1634 var past = time.Date(2009, 11, 17, 20, 34, 58, 651387237, time.UTC) 1635 var future = time.Date(2037, 11, 17, 20, 34, 58, 651387237, time.UTC) 1636 var zero = time.Time{} 1637 1638 var publishSettings = []struct { 1639 buildFuture bool 1640 buildExpired bool 1641 buildDrafts bool 1642 draft bool 1643 publishDate time.Time 1644 expiryDate time.Time 1645 out bool 1646 }{ 1647 // publishDate and expiryDate 1648 {false, false, false, false, zero, zero, true}, 1649 {false, false, false, false, zero, future, true}, 1650 {false, false, false, false, past, zero, true}, 1651 {false, false, false, false, past, future, true}, 1652 {false, false, false, false, past, past, false}, 1653 {false, false, false, false, future, future, false}, 1654 {false, false, false, false, future, past, false}, 1655 1656 // buildFuture and buildExpired 1657 {false, true, false, false, past, past, true}, 1658 {true, true, false, false, past, past, true}, 1659 {true, false, false, false, past, past, false}, 1660 {true, false, false, false, future, future, true}, 1661 {true, true, false, false, future, future, true}, 1662 {false, true, false, false, future, past, false}, 1663 1664 // buildDrafts and draft 1665 {true, true, false, true, past, future, false}, 1666 {true, true, true, true, past, future, true}, 1667 {true, true, true, true, past, future, true}, 1668 } 1669 1670 for _, ps := range publishSettings { 1671 s := shouldBuild(ps.buildFuture, ps.buildExpired, ps.buildDrafts, ps.draft, 1672 ps.publishDate, ps.expiryDate) 1673 if s != ps.out { 1674 t.Errorf("AssertShouldBuild unexpected output with params: %+v", ps) 1675 } 1676 } 1677 } 1678 1679 // "dot" in path: #1885 and #2110 1680 // disablePathToLower regression: #3374 1681 func TestPathIssues(t *testing.T) { 1682 t.Parallel() 1683 for _, disablePathToLower := range []bool{false, true} { 1684 for _, uglyURLs := range []bool{false, true} { 1685 t.Run(fmt.Sprintf("disablePathToLower=%t,uglyURLs=%t", disablePathToLower, uglyURLs), func(t *testing.T) { 1686 1687 cfg, fs := newTestCfg() 1688 th := testHelper{cfg, fs, t} 1689 1690 cfg.Set("permalinks", map[string]string{ 1691 "post": ":section/:title", 1692 }) 1693 1694 cfg.Set("uglyURLs", uglyURLs) 1695 cfg.Set("disablePathToLower", disablePathToLower) 1696 cfg.Set("paginate", 1) 1697 1698 writeSource(t, fs, filepath.Join("layouts", "_default", "single.html"), "<html><body>{{.Content}}</body></html>") 1699 writeSource(t, fs, filepath.Join("layouts", "_default", "list.html"), 1700 "<html><body>P{{.Paginator.PageNumber}}|URL: {{.Paginator.URL}}|{{ if .Paginator.HasNext }}Next: {{.Paginator.Next.URL }}{{ end }}</body></html>") 1701 1702 for i := 0; i < 3; i++ { 1703 writeSource(t, fs, filepath.Join("content", "post", fmt.Sprintf("doc%d.md", i)), 1704 fmt.Sprintf(`--- 1705 title: "test%d.dot" 1706 tags: 1707 - ".net" 1708 --- 1709 # doc1 1710 *some content*`, i)) 1711 } 1712 1713 writeSource(t, fs, filepath.Join("content", "Blog", "Blog1.md"), 1714 fmt.Sprintf(`--- 1715 title: "testBlog" 1716 tags: 1717 - "Blog" 1718 --- 1719 # doc1 1720 *some blog content*`)) 1721 1722 s := buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{}) 1723 1724 require.Len(t, s.RegularPages, 4) 1725 1726 pathFunc := func(s string) string { 1727 if uglyURLs { 1728 return strings.Replace(s, "/index.html", ".html", 1) 1729 } 1730 return s 1731 } 1732 1733 blog := "blog" 1734 1735 if disablePathToLower { 1736 blog = "Blog" 1737 } 1738 1739 th.assertFileContent(pathFunc("public/"+blog+"/"+blog+"1/index.html"), "some blog content") 1740 1741 th.assertFileContent(pathFunc("public/post/test0.dot/index.html"), "some content") 1742 1743 if uglyURLs { 1744 th.assertFileContent("public/post/page/1.html", `canonical" href="/post.html"/`) 1745 th.assertFileContent("public/post.html", `<body>P1|URL: /post.html|Next: /post/page/2.html</body>`) 1746 th.assertFileContent("public/post/page/2.html", `<body>P2|URL: /post/page/2.html|Next: /post/page/3.html</body>`) 1747 } else { 1748 th.assertFileContent("public/post/page/1/index.html", `canonical" href="/post/"/`) 1749 th.assertFileContent("public/post/index.html", `<body>P1|URL: /post/|Next: /post/page/2/</body>`) 1750 th.assertFileContent("public/post/page/2/index.html", `<body>P2|URL: /post/page/2/|Next: /post/page/3/</body>`) 1751 th.assertFileContent("public/tags/.net/index.html", `<body>P1|URL: /tags/.net/|Next: /tags/.net/page/2/</body>`) 1752 1753 } 1754 1755 p := s.RegularPages[0] 1756 if uglyURLs { 1757 require.Equal(t, "/post/test0.dot.html", p.RelPermalink()) 1758 } else { 1759 require.Equal(t, "/post/test0.dot/", p.RelPermalink()) 1760 } 1761 1762 }) 1763 } 1764 } 1765 } 1766 1767 // https://github.com/gohugoio/hugo/issues/4675 1768 func TestWordCountAndSimilarVsSummary(t *testing.T) { 1769 1770 t.Parallel() 1771 assert := require.New(t) 1772 1773 single := []string{"_default/single.html", ` 1774 WordCount: {{ .WordCount }} 1775 FuzzyWordCount: {{ .FuzzyWordCount }} 1776 ReadingTime: {{ .ReadingTime }} 1777 Len Plain: {{ len .Plain }} 1778 Len PlainWords: {{ len .PlainWords }} 1779 Truncated: {{ .Truncated }} 1780 Len Summary: {{ len .Summary }} 1781 Len Content: {{ len .Content }} 1782 `} 1783 1784 b := newTestSitesBuilder(t) 1785 b.WithSimpleConfigFile().WithTemplatesAdded(single...).WithContent("p1.md", fmt.Sprintf(`--- 1786 title: p1 1787 --- 1788 1789 %s 1790 1791 `, strings.Repeat("word ", 510)), 1792 1793 "p2.md", fmt.Sprintf(`--- 1794 title: p2 1795 --- 1796 This is a summary. 1797 1798 <!--more--> 1799 1800 %s 1801 1802 `, strings.Repeat("word ", 310)), 1803 "p3.md", fmt.Sprintf(`--- 1804 title: p3 1805 isCJKLanguage: true 1806 --- 1807 Summary: In Chinese, 好 means good. 1808 1809 <!--more--> 1810 1811 %s 1812 1813 `, strings.Repeat("好", 200)), 1814 "p4.md", fmt.Sprintf(`--- 1815 title: p4 1816 isCJKLanguage: false 1817 --- 1818 Summary: In Chinese, 好 means good. 1819 1820 <!--more--> 1821 1822 %s 1823 1824 `, strings.Repeat("好", 200)), 1825 1826 "p5.md", fmt.Sprintf(`--- 1827 title: p4 1828 isCJKLanguage: true 1829 --- 1830 Summary: In Chinese, 好 means good. 1831 1832 %s 1833 1834 `, strings.Repeat("好", 200)), 1835 "p6.md", fmt.Sprintf(`--- 1836 title: p4 1837 isCJKLanguage: false 1838 --- 1839 Summary: In Chinese, 好 means good. 1840 1841 %s 1842 1843 `, strings.Repeat("好", 200)), 1844 ) 1845 1846 b.CreateSites().Build(BuildCfg{}) 1847 1848 assert.Equal(1, len(b.H.Sites)) 1849 require.Len(t, b.H.Sites[0].RegularPages, 6) 1850 1851 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") 1852 1853 b.AssertFileContent("public/p2/index.html", "WordCount: 314\nFuzzyWordCount: 400\nReadingTime: 2\nLen Plain: 1570\nLen PlainWords: 314\nTruncated: true\nLen Summary: 34\nLen Content: 1592") 1854 1855 b.AssertFileContent("public/p3/index.html", "WordCount: 206\nFuzzyWordCount: 300\nReadingTime: 1\nLen Plain: 639\nLen PlainWords: 7\nTruncated: true\nLen Summary: 52\nLen Content: 661") 1856 b.AssertFileContent("public/p4/index.html", "WordCount: 7\nFuzzyWordCount: 100\nReadingTime: 1\nLen Plain: 639\nLen PlainWords: 7\nTruncated: true\nLen Summary: 52\nLen Content: 661") 1857 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: 653") 1858 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: 653") 1859 1860 } 1861 1862 func TestScratchSite(t *testing.T) { 1863 t.Parallel() 1864 1865 b := newTestSitesBuilder(t) 1866 b.WithSimpleConfigFile().WithTemplatesAdded("index.html", ` 1867 {{ .Scratch.Set "b" "bv" }} 1868 B: {{ .Scratch.Get "b" }} 1869 `, 1870 "shortcodes/scratch.html", ` 1871 {{ .Scratch.Set "c" "cv" }} 1872 C: {{ .Scratch.Get "c" }} 1873 `, 1874 ) 1875 1876 b.WithContentAdded("scratchme.md", ` 1877 --- 1878 title: Scratch Me! 1879 --- 1880 1881 {{< scratch >}} 1882 `) 1883 b.Build(BuildCfg{}) 1884 1885 b.AssertFileContent("public/index.html", "B: bv") 1886 b.AssertFileContent("public/scratchme/index.html", "C: cv") 1887 } 1888 1889 func BenchmarkParsePage(b *testing.B) { 1890 s := newTestSite(b) 1891 f, _ := os.Open("testdata/redis.cn.md") 1892 var buf bytes.Buffer 1893 buf.ReadFrom(f) 1894 b.ResetTimer() 1895 for i := 0; i < b.N; i++ { 1896 page, _ := s.NewPage("bench") 1897 page.ReadFrom(bytes.NewReader(buf.Bytes())) 1898 } 1899 }