github.com/olliephillips/hugo@v0.42.2/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 925 cfg.Set("enableGitInfo", true) 926 927 assrt.NoError(loadDefaultSettingsFor(cfg)) 928 929 wd, err := os.Getwd() 930 assrt.NoError(err) 931 cfg.Set("workingDir", filepath.Join(wd, "testsite")) 932 933 s := buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{SkipRender: true}) 934 935 assrt.Len(s.RegularPages, 1) 936 937 // 2018-03-11 is the Git author date for testsite/content/first-post.md 938 assrt.Equal("2018-03-11", s.RegularPages[0].Lastmod.Format("2006-01-02")) 939 } 940 941 func TestPageWithFrontMatterConfig(t *testing.T) { 942 t.Parallel() 943 944 for _, dateHandler := range []string{":filename", ":fileModTime"} { 945 t.Run(fmt.Sprintf("dateHandler=%q", dateHandler), func(t *testing.T) { 946 assrt := require.New(t) 947 cfg, fs := newTestCfg() 948 949 pageTemplate := ` 950 --- 951 title: Page 952 weight: %d 953 lastMod: 2018-02-28 954 %s 955 --- 956 Content 957 ` 958 959 cfg.Set("frontmatter", map[string]interface{}{ 960 "date": []string{dateHandler, "date"}, 961 }) 962 963 c1 := filepath.Join("content", "section", "2012-02-21-noslug.md") 964 c2 := filepath.Join("content", "section", "2012-02-22-slug.md") 965 966 writeSource(t, fs, c1, fmt.Sprintf(pageTemplate, 1, "")) 967 writeSource(t, fs, c2, fmt.Sprintf(pageTemplate, 2, "slug: aslug")) 968 969 c1fi, err := fs.Source.Stat(c1) 970 assrt.NoError(err) 971 c2fi, err := fs.Source.Stat(c2) 972 assrt.NoError(err) 973 974 s := buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{SkipRender: true}) 975 976 assrt.Len(s.RegularPages, 2) 977 978 noSlug := s.RegularPages[0] 979 slug := s.RegularPages[1] 980 981 assrt.Equal(28, noSlug.Lastmod.Day()) 982 983 switch strings.ToLower(dateHandler) { 984 case ":filename": 985 assrt.False(noSlug.Date.IsZero()) 986 assrt.False(slug.Date.IsZero()) 987 assrt.Equal(2012, noSlug.Date.Year()) 988 assrt.Equal(2012, slug.Date.Year()) 989 assrt.Equal("noslug", noSlug.Slug) 990 assrt.Equal("aslug", slug.Slug) 991 case ":filemodtime": 992 assrt.Equal(c1fi.ModTime().Year(), noSlug.Date.Year()) 993 assrt.Equal(c2fi.ModTime().Year(), slug.Date.Year()) 994 fallthrough 995 default: 996 assrt.Equal("", noSlug.Slug) 997 assrt.Equal("aslug", slug.Slug) 998 999 } 1000 }) 1001 } 1002 1003 } 1004 1005 func TestWordCountWithAllCJKRunesWithoutHasCJKLanguage(t *testing.T) { 1006 t.Parallel() 1007 assertFunc := func(t *testing.T, ext string, pages Pages) { 1008 p := pages[0] 1009 if p.WordCount() != 8 { 1010 t.Fatalf("[%s] incorrect word count for content '%s'. expected %v, got %v", ext, p.plain, 8, p.WordCount()) 1011 } 1012 } 1013 1014 testAllMarkdownEnginesForPages(t, assertFunc, nil, simplePageWithAllCJKRunes) 1015 } 1016 1017 func TestWordCountWithAllCJKRunesHasCJKLanguage(t *testing.T) { 1018 t.Parallel() 1019 settings := map[string]interface{}{"hasCJKLanguage": true} 1020 1021 assertFunc := func(t *testing.T, ext string, pages Pages) { 1022 p := pages[0] 1023 if p.WordCount() != 15 { 1024 t.Fatalf("[%s] incorrect word count for content '%s'. expected %v, got %v", ext, p.plain, 15, p.WordCount()) 1025 } 1026 } 1027 testAllMarkdownEnginesForPages(t, assertFunc, settings, simplePageWithAllCJKRunes) 1028 } 1029 1030 func TestWordCountWithMainEnglishWithCJKRunes(t *testing.T) { 1031 t.Parallel() 1032 settings := map[string]interface{}{"hasCJKLanguage": true} 1033 1034 assertFunc := func(t *testing.T, ext string, pages Pages) { 1035 p := pages[0] 1036 if p.WordCount() != 74 { 1037 t.Fatalf("[%s] incorrect word count for content '%s'. expected %v, got %v", ext, p.plain, 74, p.WordCount()) 1038 } 1039 1040 if p.summary != simplePageWithMainEnglishWithCJKRunesSummary { 1041 t.Fatalf("[%s] incorrect Summary for content '%s'. expected %v, got %v", ext, p.plain, 1042 simplePageWithMainEnglishWithCJKRunesSummary, p.summary) 1043 } 1044 } 1045 1046 testAllMarkdownEnginesForPages(t, assertFunc, settings, simplePageWithMainEnglishWithCJKRunes) 1047 } 1048 1049 func TestWordCountWithIsCJKLanguageFalse(t *testing.T) { 1050 t.Parallel() 1051 settings := map[string]interface{}{ 1052 "hasCJKLanguage": true, 1053 } 1054 1055 assertFunc := func(t *testing.T, ext string, pages Pages) { 1056 p := pages[0] 1057 if p.WordCount() != 75 { 1058 t.Fatalf("[%s] incorrect word count for content '%s'. expected %v, got %v", ext, p.plain, 74, p.WordCount()) 1059 } 1060 1061 if p.summary != simplePageWithIsCJKLanguageFalseSummary { 1062 t.Fatalf("[%s] incorrect Summary for content '%s'. expected %v, got %v", ext, p.plain, 1063 simplePageWithIsCJKLanguageFalseSummary, p.summary) 1064 } 1065 } 1066 1067 testAllMarkdownEnginesForPages(t, assertFunc, settings, simplePageWithIsCJKLanguageFalse) 1068 1069 } 1070 1071 func TestWordCount(t *testing.T) { 1072 t.Parallel() 1073 assertFunc := func(t *testing.T, ext string, pages Pages) { 1074 p := pages[0] 1075 if p.WordCount() != 483 { 1076 t.Fatalf("[%s] incorrect word count. expected %v, got %v", ext, 483, p.WordCount()) 1077 } 1078 1079 if p.FuzzyWordCount() != 500 { 1080 t.Fatalf("[%s] incorrect word count. expected %v, got %v", ext, 500, p.WordCount()) 1081 } 1082 1083 if p.ReadingTime() != 3 { 1084 t.Fatalf("[%s] incorrect min read. expected %v, got %v", ext, 3, p.ReadingTime()) 1085 } 1086 1087 checkTruncation(t, p, true, "long page") 1088 } 1089 1090 testAllMarkdownEnginesForPages(t, assertFunc, nil, simplePageWithLongContent) 1091 } 1092 1093 func TestCreatePage(t *testing.T) { 1094 t.Parallel() 1095 var tests = []struct { 1096 r string 1097 }{ 1098 {simplePageJSON}, 1099 {simplePageJSONMultiple}, 1100 //{strings.NewReader(SIMPLE_PAGE_JSON_COMPACT)}, 1101 } 1102 1103 for i, test := range tests { 1104 s := newTestSite(t) 1105 p, _ := s.NewPage("page") 1106 if _, err := p.ReadFrom(strings.NewReader(test.r)); err != nil { 1107 t.Fatalf("[%d] Unable to parse page: %s", i, err) 1108 } 1109 } 1110 } 1111 1112 func TestDegenerateInvalidFrontMatterShortDelim(t *testing.T) { 1113 t.Parallel() 1114 var tests = []struct { 1115 r string 1116 err string 1117 }{ 1118 {invalidFrontmatterShortDelimEnding, "unable to read frontmatter at filepos 45: EOF"}, 1119 } 1120 for _, test := range tests { 1121 s := newTestSite(t) 1122 p, _ := s.NewPage("invalid/front/matter/short/delim") 1123 _, err := p.ReadFrom(strings.NewReader(test.r)) 1124 checkError(t, err, test.err) 1125 } 1126 } 1127 1128 func TestShouldRenderContent(t *testing.T) { 1129 t.Parallel() 1130 var tests = []struct { 1131 text string 1132 render bool 1133 }{ 1134 {contentNoFrontmatter, true}, 1135 // TODO how to deal with malformed frontmatter. In this case it'll be rendered as markdown. 1136 {invalidFrontmatterShortDelim, true}, 1137 {renderNoFrontmatter, false}, 1138 {contentWithCommentedFrontmatter, true}, 1139 {contentWithCommentedTextFrontmatter, true}, 1140 {contentWithCommentedLongFrontmatter, false}, 1141 {contentWithCommentedLong2Frontmatter, true}, 1142 } 1143 1144 for _, test := range tests { 1145 s := newTestSite(t) 1146 p, _ := s.NewPage("render/front/matter") 1147 _, err := p.ReadFrom(strings.NewReader(test.text)) 1148 p = pageMust(p, err) 1149 if p.IsRenderable() != test.render { 1150 t.Errorf("expected p.IsRenderable() == %t, got %t", test.render, p.IsRenderable()) 1151 } 1152 } 1153 } 1154 1155 // Issue #768 1156 func TestCalendarParamsVariants(t *testing.T) { 1157 t.Parallel() 1158 s := newTestSite(t) 1159 pageJSON, _ := s.NewPage("test/fileJSON.md") 1160 _, _ = pageJSON.ReadFrom(strings.NewReader(pageWithCalendarJSONFrontmatter)) 1161 1162 pageYAML, _ := s.NewPage("test/fileYAML.md") 1163 _, _ = pageYAML.ReadFrom(strings.NewReader(pageWithCalendarYAMLFrontmatter)) 1164 1165 pageTOML, _ := s.NewPage("test/fileTOML.md") 1166 _, _ = pageTOML.ReadFrom(strings.NewReader(pageWithCalendarTOMLFrontmatter)) 1167 1168 assert.True(t, compareObjects(pageJSON.params, pageYAML.params)) 1169 assert.True(t, compareObjects(pageJSON.params, pageTOML.params)) 1170 1171 } 1172 1173 func TestDifferentFrontMatterVarTypes(t *testing.T) { 1174 t.Parallel() 1175 s := newTestSite(t) 1176 page, _ := s.NewPage("test/file1.md") 1177 _, _ = page.ReadFrom(strings.NewReader(pageWithVariousFrontmatterTypes)) 1178 1179 dateval, _ := time.Parse(time.RFC3339, "1979-05-27T07:32:00Z") 1180 if page.getParamToLower("a_string") != "bar" { 1181 t.Errorf("frontmatter not handling strings correctly should be %s, got: %s", "bar", page.getParamToLower("a_string")) 1182 } 1183 if page.getParamToLower("an_integer") != 1 { 1184 t.Errorf("frontmatter not handling ints correctly should be %s, got: %s", "1", page.getParamToLower("an_integer")) 1185 } 1186 if page.getParamToLower("a_float") != 1.3 { 1187 t.Errorf("frontmatter not handling floats correctly should be %f, got: %s", 1.3, page.getParamToLower("a_float")) 1188 } 1189 if page.getParamToLower("a_bool") != false { 1190 t.Errorf("frontmatter not handling bools correctly should be %t, got: %s", false, page.getParamToLower("a_bool")) 1191 } 1192 if page.getParamToLower("a_date") != dateval { 1193 t.Errorf("frontmatter not handling dates correctly should be %s, got: %s", dateval, page.getParamToLower("a_date")) 1194 } 1195 param := page.getParamToLower("a_table") 1196 if param == nil { 1197 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)) 1198 } 1199 if cast.ToStringMap(param)["a_key"] != "a_value" { 1200 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"]) 1201 } 1202 } 1203 1204 func TestDegenerateInvalidFrontMatterLeadingWhitespace(t *testing.T) { 1205 t.Parallel() 1206 s := newTestSite(t) 1207 p, _ := s.NewPage("invalid/front/matter/leading/ws") 1208 _, err := p.ReadFrom(strings.NewReader(invalidFrontmatterLadingWs)) 1209 if err != nil { 1210 t.Fatalf("Unable to parse front matter given leading whitespace: %s", err) 1211 } 1212 } 1213 1214 func TestSectionEvaluation(t *testing.T) { 1215 t.Parallel() 1216 s := newTestSite(t) 1217 page, _ := s.NewPage(filepath.FromSlash("blue/file1.md")) 1218 page.ReadFrom(strings.NewReader(simplePage)) 1219 if page.Section() != "blue" { 1220 t.Errorf("Section should be %s, got: %s", "blue", page.Section()) 1221 } 1222 } 1223 1224 func TestSliceToLower(t *testing.T) { 1225 t.Parallel() 1226 tests := []struct { 1227 value []string 1228 expected []string 1229 }{ 1230 {[]string{"a", "b", "c"}, []string{"a", "b", "c"}}, 1231 {[]string{"a", "B", "c"}, []string{"a", "b", "c"}}, 1232 {[]string{"A", "B", "C"}, []string{"a", "b", "c"}}, 1233 } 1234 1235 for _, test := range tests { 1236 res := helpers.SliceToLower(test.value) 1237 for i, val := range res { 1238 if val != test.expected[i] { 1239 t.Errorf("Case mismatch. Expected %s, got %s", test.expected[i], res[i]) 1240 } 1241 } 1242 } 1243 } 1244 1245 func TestReplaceDivider(t *testing.T) { 1246 t.Parallel() 1247 1248 tests := []struct { 1249 content string 1250 from string 1251 to string 1252 expectedContent string 1253 expectedTruncated bool 1254 }{ 1255 {"none", "a", "b", "none", false}, 1256 {"summary <!--more--> content", "<!--more-->", "HUGO", "summary HUGO content", true}, 1257 {"summary\n\ndivider", "divider", "HUGO", "summary\n\nHUGO", false}, 1258 {"summary\n\ndivider\n\r", "divider", "HUGO", "summary\n\nHUGO\n\r", false}, 1259 } 1260 1261 for i, test := range tests { 1262 replaced, truncated := replaceDivider([]byte(test.content), []byte(test.from), []byte(test.to)) 1263 1264 if truncated != test.expectedTruncated { 1265 t.Fatalf("[%d] Expected truncated to be %t, was %t", i, test.expectedTruncated, truncated) 1266 } 1267 1268 if string(replaced) != test.expectedContent { 1269 t.Fatalf("[%d] Expected content to be %q, was %q", i, test.expectedContent, replaced) 1270 } 1271 } 1272 } 1273 1274 func BenchmarkReplaceDivider(b *testing.B) { 1275 divider := "HUGO_DIVIDER" 1276 from, to := []byte(divider), []byte("HUGO_REPLACED") 1277 1278 withDivider := make([][]byte, b.N) 1279 noDivider := make([][]byte, b.N) 1280 1281 for i := 0; i < b.N; i++ { 1282 withDivider[i] = []byte(strings.Repeat("Summary ", 5) + "\n" + divider + "\n" + strings.Repeat("Word ", 300)) 1283 noDivider[i] = []byte(strings.Repeat("Word ", 300)) 1284 } 1285 1286 b.ResetTimer() 1287 for i := 0; i < b.N; i++ { 1288 _, t1 := replaceDivider(withDivider[i], from, to) 1289 _, t2 := replaceDivider(noDivider[i], from, to) 1290 if !t1 { 1291 b.Fatal("Should be truncated") 1292 } 1293 if t2 { 1294 b.Fatal("Should not be truncated") 1295 } 1296 } 1297 } 1298 1299 func TestPagePaths(t *testing.T) { 1300 t.Parallel() 1301 1302 siteParmalinksSetting := map[string]string{ 1303 "post": ":year/:month/:day/:title/", 1304 } 1305 1306 tests := []struct { 1307 content string 1308 path string 1309 hasPermalink bool 1310 expected string 1311 }{ 1312 {simplePage, "post/x.md", false, "post/x.html"}, 1313 {simplePageWithURL, "post/x.md", false, "simple/url/index.html"}, 1314 {simplePageWithSlug, "post/x.md", false, "post/simple-slug.html"}, 1315 {simplePageWithDate, "post/x.md", true, "2013/10/15/simple/index.html"}, 1316 {UTF8Page, "post/x.md", false, "post/x.html"}, 1317 {UTF8PageWithURL, "post/x.md", false, "ラーメン/url/index.html"}, 1318 {UTF8PageWithSlug, "post/x.md", false, "post/ラーメン-slug.html"}, 1319 {UTF8PageWithDate, "post/x.md", true, "2013/10/15/ラーメン/index.html"}, 1320 } 1321 1322 for _, test := range tests { 1323 cfg, fs := newTestCfg() 1324 1325 if test.hasPermalink { 1326 cfg.Set("permalinks", siteParmalinksSetting) 1327 } 1328 1329 writeSource(t, fs, filepath.Join("content", filepath.FromSlash(test.path)), test.content) 1330 1331 s := buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{SkipRender: true}) 1332 require.Len(t, s.RegularPages, 1) 1333 1334 } 1335 } 1336 1337 var pageWithDraftAndPublished = `--- 1338 title: broken 1339 published: false 1340 draft: true 1341 --- 1342 some content 1343 ` 1344 1345 func TestDraftAndPublishedFrontMatterError(t *testing.T) { 1346 t.Parallel() 1347 s := newTestSite(t) 1348 _, err := s.NewPageFrom(strings.NewReader(pageWithDraftAndPublished), "content/post/broken.md") 1349 if err != ErrHasDraftAndPublished { 1350 t.Errorf("expected ErrHasDraftAndPublished, was %#v", err) 1351 } 1352 } 1353 1354 var pagesWithPublishedFalse = `--- 1355 title: okay 1356 published: false 1357 --- 1358 some content 1359 ` 1360 var pageWithPublishedTrue = `--- 1361 title: okay 1362 published: true 1363 --- 1364 some content 1365 ` 1366 1367 func TestPublishedFrontMatter(t *testing.T) { 1368 t.Parallel() 1369 s := newTestSite(t) 1370 p, err := s.NewPageFrom(strings.NewReader(pagesWithPublishedFalse), "content/post/broken.md") 1371 if err != nil { 1372 t.Fatalf("err during parse: %s", err) 1373 } 1374 if !p.Draft { 1375 t.Errorf("expected true, got %t", p.Draft) 1376 } 1377 p, err = s.NewPageFrom(strings.NewReader(pageWithPublishedTrue), "content/post/broken.md") 1378 if err != nil { 1379 t.Fatalf("err during parse: %s", err) 1380 } 1381 if p.Draft { 1382 t.Errorf("expected false, got %t", p.Draft) 1383 } 1384 } 1385 1386 var pagesDraftTemplate = []string{`--- 1387 title: "okay" 1388 draft: %t 1389 --- 1390 some content 1391 `, 1392 `+++ 1393 title = "okay" 1394 draft = %t 1395 +++ 1396 1397 some content 1398 `, 1399 } 1400 1401 func TestDraft(t *testing.T) { 1402 t.Parallel() 1403 s := newTestSite(t) 1404 for _, draft := range []bool{true, false} { 1405 for i, templ := range pagesDraftTemplate { 1406 pageContent := fmt.Sprintf(templ, draft) 1407 p, err := s.NewPageFrom(strings.NewReader(pageContent), "content/post/broken.md") 1408 if err != nil { 1409 t.Fatalf("err during parse: %s", err) 1410 } 1411 if p.Draft != draft { 1412 t.Errorf("[%d] expected %t, got %t", i, draft, p.Draft) 1413 } 1414 } 1415 } 1416 } 1417 1418 var pagesParamsTemplate = []string{`+++ 1419 title = "okay" 1420 draft = false 1421 tags = [ "hugo", "web" ] 1422 social= [ 1423 [ "a", "#" ], 1424 [ "b", "#" ], 1425 ] 1426 +++ 1427 some content 1428 `, 1429 `--- 1430 title: "okay" 1431 draft: false 1432 tags: 1433 - hugo 1434 - web 1435 social: 1436 - - a 1437 - "#" 1438 - - b 1439 - "#" 1440 --- 1441 some content 1442 `, 1443 `{ 1444 "title": "okay", 1445 "draft": false, 1446 "tags": [ "hugo", "web" ], 1447 "social": [ 1448 [ "a", "#" ], 1449 [ "b", "#" ] 1450 ] 1451 } 1452 some content 1453 `, 1454 } 1455 1456 func TestPageParams(t *testing.T) { 1457 t.Parallel() 1458 s := newTestSite(t) 1459 wantedMap := map[string]interface{}{ 1460 "tags": []string{"hugo", "web"}, 1461 // Issue #2752 1462 "social": []interface{}{ 1463 []interface{}{"a", "#"}, 1464 []interface{}{"b", "#"}, 1465 }, 1466 } 1467 1468 for i, c := range pagesParamsTemplate { 1469 p, err := s.NewPageFrom(strings.NewReader(c), "content/post/params.md") 1470 require.NoError(t, err, "err during parse", "#%d", i) 1471 for key := range wantedMap { 1472 assert.Equal(t, wantedMap[key], p.params[key], "#%d", key) 1473 } 1474 } 1475 } 1476 1477 func TestTraverse(t *testing.T) { 1478 exampleParams := `--- 1479 rating: "5 stars" 1480 tags: 1481 - hugo 1482 - web 1483 social: 1484 twitter: "@jxxf" 1485 facebook: "https://example.com" 1486 ---` 1487 t.Parallel() 1488 s := newTestSite(t) 1489 p, _ := s.NewPageFrom(strings.NewReader(exampleParams), "content/post/params.md") 1490 1491 topLevelKeyValue, _ := p.Param("rating") 1492 assert.Equal(t, "5 stars", topLevelKeyValue) 1493 1494 nestedStringKeyValue, _ := p.Param("social.twitter") 1495 assert.Equal(t, "@jxxf", nestedStringKeyValue) 1496 1497 nonexistentKeyValue, _ := p.Param("doesn't.exist") 1498 assert.Nil(t, nonexistentKeyValue) 1499 } 1500 1501 func TestPageSimpleMethods(t *testing.T) { 1502 t.Parallel() 1503 s := newTestSite(t) 1504 for i, this := range []struct { 1505 assertFunc func(p *Page) bool 1506 }{ 1507 {func(p *Page) bool { return !p.IsNode() }}, 1508 {func(p *Page) bool { return p.IsPage() }}, 1509 {func(p *Page) bool { return p.Plain() == "Do Be Do Be Do" }}, 1510 {func(p *Page) bool { return strings.Join(p.PlainWords(), " ") == "Do Be Do Be Do" }}, 1511 } { 1512 1513 p, _ := s.NewPage("Test") 1514 p.workContent = []byte("<h1>Do Be Do Be Do</h1>") 1515 p.resetContent() 1516 if !this.assertFunc(p) { 1517 t.Errorf("[%d] Page method error", i) 1518 } 1519 } 1520 } 1521 1522 func TestIndexPageSimpleMethods(t *testing.T) { 1523 s := newTestSite(t) 1524 t.Parallel() 1525 for i, this := range []struct { 1526 assertFunc func(n *Page) bool 1527 }{ 1528 {func(n *Page) bool { return n.IsNode() }}, 1529 {func(n *Page) bool { return !n.IsPage() }}, 1530 {func(n *Page) bool { return n.Scratch() != nil }}, 1531 {func(n *Page) bool { return n.Hugo() != nil }}, 1532 } { 1533 1534 n := s.newHomePage() 1535 1536 if !this.assertFunc(n) { 1537 t.Errorf("[%d] Node method error", i) 1538 } 1539 } 1540 } 1541 1542 func TestKind(t *testing.T) { 1543 t.Parallel() 1544 // Add tests for these constants to make sure they don't change 1545 require.Equal(t, "page", KindPage) 1546 require.Equal(t, "home", KindHome) 1547 require.Equal(t, "section", KindSection) 1548 require.Equal(t, "taxonomy", KindTaxonomy) 1549 require.Equal(t, "taxonomyTerm", KindTaxonomyTerm) 1550 1551 } 1552 1553 func TestTranslationKey(t *testing.T) { 1554 t.Parallel() 1555 assert := require.New(t) 1556 cfg, fs := newTestCfg() 1557 1558 writeSource(t, fs, filepath.Join("content", filepath.FromSlash("sect/simple.no.md")), "---\ntitle: \"A1\"\ntranslationKey: \"k1\"\n---\nContent\n") 1559 writeSource(t, fs, filepath.Join("content", filepath.FromSlash("sect/simple.en.md")), "---\ntitle: \"A2\"\n---\nContent\n") 1560 1561 s := buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{SkipRender: true}) 1562 1563 require.Len(t, s.RegularPages, 2) 1564 1565 home, _ := s.Info.Home() 1566 assert.NotNil(home) 1567 assert.Equal("home", home.TranslationKey()) 1568 assert.Equal("page/k1", s.RegularPages[0].TranslationKey()) 1569 p2 := s.RegularPages[1] 1570 1571 assert.Equal("page/sect/simple", p2.TranslationKey()) 1572 1573 } 1574 1575 func TestChompBOM(t *testing.T) { 1576 t.Parallel() 1577 const utf8BOM = "\xef\xbb\xbf" 1578 1579 cfg, fs := newTestCfg() 1580 1581 writeSource(t, fs, filepath.Join("content", "simple.md"), utf8BOM+simplePage) 1582 1583 s := buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{SkipRender: true}) 1584 1585 require.Len(t, s.RegularPages, 1) 1586 1587 p := s.RegularPages[0] 1588 1589 checkPageTitle(t, p, "Simple") 1590 } 1591 1592 // TODO(bep) this may be useful for other tests. 1593 func compareObjects(a interface{}, b interface{}) bool { 1594 aStr := strings.Split(fmt.Sprintf("%v", a), "") 1595 sort.Strings(aStr) 1596 1597 bStr := strings.Split(fmt.Sprintf("%v", b), "") 1598 sort.Strings(bStr) 1599 1600 return strings.Join(aStr, "") == strings.Join(bStr, "") 1601 } 1602 1603 func TestShouldBuild(t *testing.T) { 1604 t.Parallel() 1605 var past = time.Date(2009, 11, 17, 20, 34, 58, 651387237, time.UTC) 1606 var future = time.Date(2037, 11, 17, 20, 34, 58, 651387237, time.UTC) 1607 var zero = time.Time{} 1608 1609 var publishSettings = []struct { 1610 buildFuture bool 1611 buildExpired bool 1612 buildDrafts bool 1613 draft bool 1614 publishDate time.Time 1615 expiryDate time.Time 1616 out bool 1617 }{ 1618 // publishDate and expiryDate 1619 {false, false, false, false, zero, zero, true}, 1620 {false, false, false, false, zero, future, true}, 1621 {false, false, false, false, past, zero, true}, 1622 {false, false, false, false, past, future, true}, 1623 {false, false, false, false, past, past, false}, 1624 {false, false, false, false, future, future, false}, 1625 {false, false, false, false, future, past, false}, 1626 1627 // buildFuture and buildExpired 1628 {false, true, false, false, past, past, true}, 1629 {true, true, false, false, past, past, true}, 1630 {true, false, false, false, past, past, false}, 1631 {true, false, false, false, future, future, true}, 1632 {true, true, false, false, future, future, true}, 1633 {false, true, false, false, future, past, false}, 1634 1635 // buildDrafts and draft 1636 {true, true, false, true, past, future, false}, 1637 {true, true, true, true, past, future, true}, 1638 {true, true, true, true, past, future, true}, 1639 } 1640 1641 for _, ps := range publishSettings { 1642 s := shouldBuild(ps.buildFuture, ps.buildExpired, ps.buildDrafts, ps.draft, 1643 ps.publishDate, ps.expiryDate) 1644 if s != ps.out { 1645 t.Errorf("AssertShouldBuild unexpected output with params: %+v", ps) 1646 } 1647 } 1648 } 1649 1650 // "dot" in path: #1885 and #2110 1651 // disablePathToLower regression: #3374 1652 func TestPathIssues(t *testing.T) { 1653 t.Parallel() 1654 for _, disablePathToLower := range []bool{false, true} { 1655 for _, uglyURLs := range []bool{false, true} { 1656 t.Run(fmt.Sprintf("disablePathToLower=%t,uglyURLs=%t", disablePathToLower, uglyURLs), func(t *testing.T) { 1657 1658 cfg, fs := newTestCfg() 1659 th := testHelper{cfg, fs, t} 1660 1661 cfg.Set("permalinks", map[string]string{ 1662 "post": ":section/:title", 1663 }) 1664 1665 cfg.Set("uglyURLs", uglyURLs) 1666 cfg.Set("disablePathToLower", disablePathToLower) 1667 cfg.Set("paginate", 1) 1668 1669 writeSource(t, fs, filepath.Join("layouts", "_default", "single.html"), "<html><body>{{.Content}}</body></html>") 1670 writeSource(t, fs, filepath.Join("layouts", "_default", "list.html"), 1671 "<html><body>P{{.Paginator.PageNumber}}|URL: {{.Paginator.URL}}|{{ if .Paginator.HasNext }}Next: {{.Paginator.Next.URL }}{{ end }}</body></html>") 1672 1673 for i := 0; i < 3; i++ { 1674 writeSource(t, fs, filepath.Join("content", "post", fmt.Sprintf("doc%d.md", i)), 1675 fmt.Sprintf(`--- 1676 title: "test%d.dot" 1677 tags: 1678 - ".net" 1679 --- 1680 # doc1 1681 *some content*`, i)) 1682 } 1683 1684 writeSource(t, fs, filepath.Join("content", "Blog", "Blog1.md"), 1685 fmt.Sprintf(`--- 1686 title: "testBlog" 1687 tags: 1688 - "Blog" 1689 --- 1690 # doc1 1691 *some blog content*`)) 1692 1693 s := buildSingleSite(t, deps.DepsCfg{Fs: fs, Cfg: cfg}, BuildCfg{}) 1694 1695 require.Len(t, s.RegularPages, 4) 1696 1697 pathFunc := func(s string) string { 1698 if uglyURLs { 1699 return strings.Replace(s, "/index.html", ".html", 1) 1700 } 1701 return s 1702 } 1703 1704 blog := "blog" 1705 1706 if disablePathToLower { 1707 blog = "Blog" 1708 } 1709 1710 th.assertFileContent(pathFunc("public/"+blog+"/"+blog+"1/index.html"), "some blog content") 1711 1712 th.assertFileContent(pathFunc("public/post/test0.dot/index.html"), "some content") 1713 1714 if uglyURLs { 1715 th.assertFileContent("public/post/page/1.html", `canonical" href="/post.html"/`) 1716 th.assertFileContent("public/post.html", `<body>P1|URL: /post.html|Next: /post/page/2.html</body>`) 1717 th.assertFileContent("public/post/page/2.html", `<body>P2|URL: /post/page/2.html|Next: /post/page/3.html</body>`) 1718 } else { 1719 th.assertFileContent("public/post/page/1/index.html", `canonical" href="/post/"/`) 1720 th.assertFileContent("public/post/index.html", `<body>P1|URL: /post/|Next: /post/page/2/</body>`) 1721 th.assertFileContent("public/post/page/2/index.html", `<body>P2|URL: /post/page/2/|Next: /post/page/3/</body>`) 1722 th.assertFileContent("public/tags/.net/index.html", `<body>P1|URL: /tags/.net/|Next: /tags/.net/page/2/</body>`) 1723 1724 } 1725 1726 p := s.RegularPages[0] 1727 if uglyURLs { 1728 require.Equal(t, "/post/test0.dot.html", p.RelPermalink()) 1729 } else { 1730 require.Equal(t, "/post/test0.dot/", p.RelPermalink()) 1731 } 1732 1733 }) 1734 } 1735 } 1736 } 1737 1738 // https://github.com/gohugoio/hugo/issues/4675 1739 func TestWordCountAndSimilarVsSummary(t *testing.T) { 1740 1741 t.Parallel() 1742 assert := require.New(t) 1743 1744 single := []string{"_default/single.html", ` 1745 WordCount: {{ .WordCount }} 1746 FuzzyWordCount: {{ .FuzzyWordCount }} 1747 ReadingTime: {{ .ReadingTime }} 1748 Len Plain: {{ len .Plain }} 1749 Len PlainWords: {{ len .PlainWords }} 1750 Truncated: {{ .Truncated }} 1751 Len Summary: {{ len .Summary }} 1752 Len Content: {{ len .Content }} 1753 `} 1754 1755 b := newTestSitesBuilder(t) 1756 b.WithSimpleConfigFile().WithTemplatesAdded(single...).WithContent("p1.md", fmt.Sprintf(`--- 1757 title: p1 1758 --- 1759 1760 %s 1761 1762 `, strings.Repeat("word ", 510)), 1763 1764 "p2.md", fmt.Sprintf(`--- 1765 title: p2 1766 --- 1767 This is a summary. 1768 1769 <!--more--> 1770 1771 %s 1772 1773 `, strings.Repeat("word ", 310)), 1774 "p3.md", fmt.Sprintf(`--- 1775 title: p3 1776 isCJKLanguage: true 1777 --- 1778 Summary: In Chinese, 好 means good. 1779 1780 <!--more--> 1781 1782 %s 1783 1784 `, strings.Repeat("好", 200)), 1785 "p4.md", fmt.Sprintf(`--- 1786 title: p4 1787 isCJKLanguage: false 1788 --- 1789 Summary: In Chinese, 好 means good. 1790 1791 <!--more--> 1792 1793 %s 1794 1795 `, strings.Repeat("好", 200)), 1796 1797 "p5.md", fmt.Sprintf(`--- 1798 title: p4 1799 isCJKLanguage: true 1800 --- 1801 Summary: In Chinese, 好 means good. 1802 1803 %s 1804 1805 `, strings.Repeat("好", 200)), 1806 "p6.md", fmt.Sprintf(`--- 1807 title: p4 1808 isCJKLanguage: false 1809 --- 1810 Summary: In Chinese, 好 means good. 1811 1812 %s 1813 1814 `, strings.Repeat("好", 200)), 1815 ) 1816 1817 b.CreateSites().Build(BuildCfg{}) 1818 1819 assert.Equal(1, len(b.H.Sites)) 1820 require.Len(t, b.H.Sites[0].RegularPages, 6) 1821 1822 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") 1823 1824 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") 1825 1826 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") 1827 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") 1828 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") 1829 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") 1830 1831 } 1832 1833 func BenchmarkParsePage(b *testing.B) { 1834 s := newTestSite(b) 1835 f, _ := os.Open("testdata/redis.cn.md") 1836 var buf bytes.Buffer 1837 buf.ReadFrom(f) 1838 b.ResetTimer() 1839 for i := 0; i < b.N; i++ { 1840 page, _ := s.NewPage("bench") 1841 page.ReadFrom(bytes.NewReader(buf.Bytes())) 1842 } 1843 }