github.com/olliephillips/hugo@v0.42.2/helpers/content_test.go (about) 1 // Copyright 2015 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 helpers 15 16 import ( 17 "bytes" 18 "html/template" 19 "strings" 20 "testing" 21 22 "github.com/spf13/viper" 23 24 "github.com/miekg/mmark" 25 "github.com/russross/blackfriday" 26 "github.com/stretchr/testify/assert" 27 "github.com/stretchr/testify/require" 28 ) 29 30 const tstHTMLContent = "<!DOCTYPE html><html><head><script src=\"http://two/foobar.js\"></script></head><body><nav><ul><li hugo-nav=\"section_0\"></li><li hugo-nav=\"section_1\"></li></ul></nav><article>content <a href=\"http://two/foobar\">foobar</a>. Follow up</article><p>This is some text.<br>And some more.</p></body></html>" 31 32 func TestStripHTML(t *testing.T) { 33 type test struct { 34 input, expected string 35 } 36 data := []test{ 37 {"<h1>strip h1 tag <h1>", "strip h1 tag "}, 38 {"<p> strip p tag </p>", " strip p tag "}, 39 {"</br> strip br<br>", " strip br\n"}, 40 {"</br> strip br2<br />", " strip br2\n"}, 41 {"This <strong>is</strong> a\nnewline", "This is a newline"}, 42 {"No Tags", "No Tags"}, 43 {`<p>Summary Next Line. 44 <figure > 45 46 <img src="/not/real" /> 47 48 49 </figure> 50 . 51 More text here.</p> 52 53 <p>Some more text</p>`, "Summary Next Line. . More text here.\nSome more text\n"}, 54 } 55 for i, d := range data { 56 output := StripHTML(d.input) 57 if d.expected != output { 58 t.Errorf("Test %d failed. Expected %q got %q", i, d.expected, output) 59 } 60 } 61 } 62 63 func BenchmarkStripHTML(b *testing.B) { 64 b.ResetTimer() 65 for i := 0; i < b.N; i++ { 66 StripHTML(tstHTMLContent) 67 } 68 } 69 70 func TestStripEmptyNav(t *testing.T) { 71 cleaned := stripEmptyNav([]byte("do<nav>\n</nav>\n\nbedobedo")) 72 assert.Equal(t, []byte("dobedobedo"), cleaned) 73 } 74 75 func TestBytesToHTML(t *testing.T) { 76 assert.Equal(t, template.HTML("dobedobedo"), BytesToHTML([]byte("dobedobedo"))) 77 } 78 79 func TestNewContentSpec(t *testing.T) { 80 cfg := viper.New() 81 assert := require.New(t) 82 83 cfg.Set("summaryLength", 32) 84 cfg.Set("buildFuture", true) 85 cfg.Set("buildExpired", true) 86 cfg.Set("buildDrafts", true) 87 88 spec, err := NewContentSpec(cfg) 89 90 assert.NoError(err) 91 assert.Equal(32, spec.summaryLength) 92 assert.True(spec.BuildFuture) 93 assert.True(spec.BuildExpired) 94 assert.True(spec.BuildDrafts) 95 96 } 97 98 var benchmarkTruncateString = strings.Repeat("This is a sentence about nothing.", 20) 99 100 func BenchmarkTestTruncateWordsToWholeSentence(b *testing.B) { 101 c := newTestContentSpec() 102 b.ResetTimer() 103 for i := 0; i < b.N; i++ { 104 c.TruncateWordsToWholeSentence(benchmarkTruncateString) 105 } 106 } 107 108 func BenchmarkTestTruncateWordsToWholeSentenceOld(b *testing.B) { 109 c := newTestContentSpec() 110 b.ResetTimer() 111 for i := 0; i < b.N; i++ { 112 c.truncateWordsToWholeSentenceOld(benchmarkTruncateString) 113 } 114 } 115 116 func TestTruncateWordsToWholeSentence(t *testing.T) { 117 c := newTestContentSpec() 118 type test struct { 119 input, expected string 120 max int 121 truncated bool 122 } 123 data := []test{ 124 {"a b c", "a b c", 12, false}, 125 {"a b c", "a b c", 3, false}, 126 {"a", "a", 1, false}, 127 {"This is a sentence.", "This is a sentence.", 5, false}, 128 {"This is also a sentence!", "This is also a sentence!", 1, false}, 129 {"To be. Or not to be. That's the question.", "To be.", 1, true}, 130 {" \nThis is not a sentence\nAnd this is another", "This is not a sentence", 4, true}, 131 {"", "", 10, false}, 132 {"This... is a more difficult test?", "This... is a more difficult test?", 1, false}, 133 } 134 for i, d := range data { 135 c.summaryLength = d.max 136 output, truncated := c.TruncateWordsToWholeSentence(d.input) 137 if d.expected != output { 138 t.Errorf("Test %d failed. Expected %q got %q", i, d.expected, output) 139 } 140 141 if d.truncated != truncated { 142 t.Errorf("Test %d failed. Expected truncated=%t got %t", i, d.truncated, truncated) 143 } 144 } 145 } 146 147 func TestTruncateWordsByRune(t *testing.T) { 148 c := newTestContentSpec() 149 type test struct { 150 input, expected string 151 max int 152 truncated bool 153 } 154 data := []test{ 155 {"", "", 1, false}, 156 {"a b c", "a b c", 12, false}, 157 {"a b c", "a b c", 3, false}, 158 {"a", "a", 1, false}, 159 {"Hello 中国", "", 0, true}, 160 {"这是中文,全中文。", "这是中文,", 5, true}, 161 {"Hello 中国", "Hello 中", 2, true}, 162 {"Hello 中国", "Hello 中国", 3, false}, 163 {"Hello中国 Good 好的", "Hello中国 Good 好", 9, true}, 164 {"This is a sentence.", "This is", 2, true}, 165 {"This is also a sentence!", "This", 1, true}, 166 {"To be. Or not to be. That's the question.", "To be. Or not", 4, true}, 167 {" \nThis is not a sentence\n ", "This is not", 3, true}, 168 } 169 for i, d := range data { 170 c.summaryLength = d.max 171 output, truncated := c.TruncateWordsByRune(strings.Fields(d.input)) 172 if d.expected != output { 173 t.Errorf("Test %d failed. Expected %q got %q", i, d.expected, output) 174 } 175 176 if d.truncated != truncated { 177 t.Errorf("Test %d failed. Expected truncated=%t got %t", i, d.truncated, truncated) 178 } 179 } 180 } 181 182 func TestGetHTMLRendererFlags(t *testing.T) { 183 c := newTestContentSpec() 184 ctx := &RenderingContext{Cfg: c.cfg, Config: c.BlackFriday} 185 renderer := c.getHTMLRenderer(blackfriday.HTML_USE_XHTML, ctx) 186 flags := renderer.GetFlags() 187 if flags&blackfriday.HTML_USE_XHTML != blackfriday.HTML_USE_XHTML { 188 t.Errorf("Test flag: %d was not found amongs set flags:%d; Result: %d", blackfriday.HTML_USE_XHTML, flags, flags&blackfriday.HTML_USE_XHTML) 189 } 190 } 191 192 func TestGetHTMLRendererAllFlags(t *testing.T) { 193 c := newTestContentSpec() 194 195 type data struct { 196 testFlag int 197 } 198 199 allFlags := []data{ 200 {blackfriday.HTML_USE_XHTML}, 201 {blackfriday.HTML_FOOTNOTE_RETURN_LINKS}, 202 {blackfriday.HTML_USE_SMARTYPANTS}, 203 {blackfriday.HTML_SMARTYPANTS_QUOTES_NBSP}, 204 {blackfriday.HTML_SMARTYPANTS_ANGLED_QUOTES}, 205 {blackfriday.HTML_SMARTYPANTS_FRACTIONS}, 206 {blackfriday.HTML_HREF_TARGET_BLANK}, 207 {blackfriday.HTML_NOFOLLOW_LINKS}, 208 {blackfriday.HTML_NOREFERRER_LINKS}, 209 {blackfriday.HTML_SMARTYPANTS_DASHES}, 210 {blackfriday.HTML_SMARTYPANTS_LATEX_DASHES}, 211 } 212 defaultFlags := blackfriday.HTML_USE_XHTML 213 ctx := &RenderingContext{Cfg: c.cfg, Config: c.BlackFriday} 214 ctx.Config.AngledQuotes = true 215 ctx.Config.Fractions = true 216 ctx.Config.HrefTargetBlank = true 217 ctx.Config.NofollowLinks = true 218 ctx.Config.NoreferrerLinks = true 219 ctx.Config.LatexDashes = true 220 ctx.Config.PlainIDAnchors = true 221 ctx.Config.SmartDashes = true 222 ctx.Config.Smartypants = true 223 ctx.Config.SmartypantsQuotesNBSP = true 224 renderer := c.getHTMLRenderer(defaultFlags, ctx) 225 actualFlags := renderer.GetFlags() 226 var expectedFlags int 227 //OR-ing flags together... 228 for _, d := range allFlags { 229 expectedFlags |= d.testFlag 230 } 231 if expectedFlags != actualFlags { 232 t.Errorf("Expected flags (%d) did not equal actual (%d) flags.", expectedFlags, actualFlags) 233 } 234 } 235 236 func TestGetHTMLRendererAnchors(t *testing.T) { 237 c := newTestContentSpec() 238 ctx := &RenderingContext{Cfg: c.cfg, Config: c.BlackFriday} 239 ctx.DocumentID = "testid" 240 ctx.Config.PlainIDAnchors = false 241 242 actualRenderer := c.getHTMLRenderer(0, ctx) 243 headerBuffer := &bytes.Buffer{} 244 footnoteBuffer := &bytes.Buffer{} 245 expectedFootnoteHref := []byte("href=\"#fn:testid:href\"") 246 expectedHeaderID := []byte("<h1 id=\"id:testid\"></h1>\n") 247 248 actualRenderer.Header(headerBuffer, func() bool { return true }, 1, "id") 249 actualRenderer.FootnoteRef(footnoteBuffer, []byte("href"), 1) 250 251 if !bytes.Contains(footnoteBuffer.Bytes(), expectedFootnoteHref) { 252 t.Errorf("Footnote anchor prefix not applied. Actual:%s Expected:%s", footnoteBuffer.String(), expectedFootnoteHref) 253 } 254 255 if !bytes.Equal(headerBuffer.Bytes(), expectedHeaderID) { 256 t.Errorf("Header Id Postfix not applied. Actual:%s Expected:%s", headerBuffer.String(), expectedHeaderID) 257 } 258 } 259 260 func TestGetMmarkHTMLRenderer(t *testing.T) { 261 c := newTestContentSpec() 262 ctx := &RenderingContext{Cfg: c.cfg, Config: c.BlackFriday} 263 ctx.DocumentID = "testid" 264 ctx.Config.PlainIDAnchors = false 265 actualRenderer := c.getMmarkHTMLRenderer(0, ctx) 266 267 headerBuffer := &bytes.Buffer{} 268 footnoteBuffer := &bytes.Buffer{} 269 expectedFootnoteHref := []byte("href=\"#fn:testid:href\"") 270 expectedHeaderID := []byte("<h1 id=\"id\"></h1>") 271 272 actualRenderer.FootnoteRef(footnoteBuffer, []byte("href"), 1) 273 actualRenderer.Header(headerBuffer, func() bool { return true }, 1, "id") 274 275 if !bytes.Contains(footnoteBuffer.Bytes(), expectedFootnoteHref) { 276 t.Errorf("Footnote anchor prefix not applied. Actual:%s Expected:%s", footnoteBuffer.String(), expectedFootnoteHref) 277 } 278 279 if bytes.Equal(headerBuffer.Bytes(), expectedHeaderID) { 280 t.Errorf("Header Id Postfix applied. Actual:%s Expected:%s", headerBuffer.String(), expectedHeaderID) 281 } 282 } 283 284 func TestGetMarkdownExtensionsMasksAreRemovedFromExtensions(t *testing.T) { 285 c := newTestContentSpec() 286 ctx := &RenderingContext{Cfg: c.cfg, Config: c.BlackFriday} 287 ctx.Config.Extensions = []string{"headerId"} 288 ctx.Config.ExtensionsMask = []string{"noIntraEmphasis"} 289 290 actualFlags := getMarkdownExtensions(ctx) 291 if actualFlags&blackfriday.EXTENSION_NO_INTRA_EMPHASIS == blackfriday.EXTENSION_NO_INTRA_EMPHASIS { 292 t.Errorf("Masked out flag {%v} found amongst returned extensions.", blackfriday.EXTENSION_NO_INTRA_EMPHASIS) 293 } 294 } 295 296 func TestGetMarkdownExtensionsByDefaultAllExtensionsAreEnabled(t *testing.T) { 297 type data struct { 298 testFlag int 299 } 300 c := newTestContentSpec() 301 ctx := &RenderingContext{Cfg: c.cfg, Config: c.BlackFriday} 302 ctx.Config.Extensions = []string{""} 303 ctx.Config.ExtensionsMask = []string{""} 304 allExtensions := []data{ 305 {blackfriday.EXTENSION_NO_INTRA_EMPHASIS}, 306 {blackfriday.EXTENSION_TABLES}, 307 {blackfriday.EXTENSION_FENCED_CODE}, 308 {blackfriday.EXTENSION_AUTOLINK}, 309 {blackfriday.EXTENSION_STRIKETHROUGH}, 310 // {blackfriday.EXTENSION_LAX_HTML_BLOCKS}, 311 {blackfriday.EXTENSION_SPACE_HEADERS}, 312 // {blackfriday.EXTENSION_HARD_LINE_BREAK}, 313 // {blackfriday.EXTENSION_TAB_SIZE_EIGHT}, 314 {blackfriday.EXTENSION_FOOTNOTES}, 315 // {blackfriday.EXTENSION_NO_EMPTY_LINE_BEFORE_BLOCK}, 316 {blackfriday.EXTENSION_HEADER_IDS}, 317 // {blackfriday.EXTENSION_TITLEBLOCK}, 318 {blackfriday.EXTENSION_AUTO_HEADER_IDS}, 319 {blackfriday.EXTENSION_BACKSLASH_LINE_BREAK}, 320 {blackfriday.EXTENSION_DEFINITION_LISTS}, 321 } 322 323 actualFlags := getMarkdownExtensions(ctx) 324 for _, e := range allExtensions { 325 if actualFlags&e.testFlag != e.testFlag { 326 t.Errorf("Flag %v was not found in the list of extensions.", e) 327 } 328 } 329 } 330 331 func TestGetMarkdownExtensionsAddingFlagsThroughRenderingContext(t *testing.T) { 332 c := newTestContentSpec() 333 ctx := &RenderingContext{Cfg: c.cfg, Config: c.BlackFriday} 334 ctx.Config.Extensions = []string{"definitionLists"} 335 ctx.Config.ExtensionsMask = []string{""} 336 337 actualFlags := getMarkdownExtensions(ctx) 338 if actualFlags&blackfriday.EXTENSION_DEFINITION_LISTS != blackfriday.EXTENSION_DEFINITION_LISTS { 339 t.Errorf("Masked out flag {%v} found amongst returned extensions.", blackfriday.EXTENSION_DEFINITION_LISTS) 340 } 341 } 342 343 func TestGetMarkdownRenderer(t *testing.T) { 344 c := newTestContentSpec() 345 ctx := &RenderingContext{Cfg: c.cfg, Config: c.BlackFriday} 346 ctx.Content = []byte("testContent") 347 actualRenderedMarkdown := c.markdownRender(ctx) 348 expectedRenderedMarkdown := []byte("<p>testContent</p>\n") 349 if !bytes.Equal(actualRenderedMarkdown, expectedRenderedMarkdown) { 350 t.Errorf("Actual rendered Markdown (%s) did not match expected markdown (%s)", actualRenderedMarkdown, expectedRenderedMarkdown) 351 } 352 } 353 354 func TestGetMarkdownRendererWithTOC(t *testing.T) { 355 c := newTestContentSpec() 356 ctx := &RenderingContext{RenderTOC: true, Cfg: c.cfg, Config: c.BlackFriday} 357 ctx.Content = []byte("testContent") 358 actualRenderedMarkdown := c.markdownRender(ctx) 359 expectedRenderedMarkdown := []byte("<nav>\n</nav>\n\n<p>testContent</p>\n") 360 if !bytes.Equal(actualRenderedMarkdown, expectedRenderedMarkdown) { 361 t.Errorf("Actual rendered Markdown (%s) did not match expected markdown (%s)", actualRenderedMarkdown, expectedRenderedMarkdown) 362 } 363 } 364 365 func TestGetMmarkExtensions(t *testing.T) { 366 //TODO: This is doing the same just with different marks... 367 type data struct { 368 testFlag int 369 } 370 c := newTestContentSpec() 371 ctx := &RenderingContext{Cfg: c.cfg, Config: c.BlackFriday} 372 ctx.Config.Extensions = []string{"tables"} 373 ctx.Config.ExtensionsMask = []string{""} 374 allExtensions := []data{ 375 {mmark.EXTENSION_TABLES}, 376 {mmark.EXTENSION_FENCED_CODE}, 377 {mmark.EXTENSION_AUTOLINK}, 378 {mmark.EXTENSION_SPACE_HEADERS}, 379 {mmark.EXTENSION_CITATION}, 380 {mmark.EXTENSION_TITLEBLOCK_TOML}, 381 {mmark.EXTENSION_HEADER_IDS}, 382 {mmark.EXTENSION_AUTO_HEADER_IDS}, 383 {mmark.EXTENSION_UNIQUE_HEADER_IDS}, 384 {mmark.EXTENSION_FOOTNOTES}, 385 {mmark.EXTENSION_SHORT_REF}, 386 {mmark.EXTENSION_NO_EMPTY_LINE_BEFORE_BLOCK}, 387 {mmark.EXTENSION_INCLUDE}, 388 } 389 390 actualFlags := getMmarkExtensions(ctx) 391 for _, e := range allExtensions { 392 if actualFlags&e.testFlag != e.testFlag { 393 t.Errorf("Flag %v was not found in the list of extensions.", e) 394 } 395 } 396 } 397 398 func TestMmarkRender(t *testing.T) { 399 c := newTestContentSpec() 400 ctx := &RenderingContext{Cfg: c.cfg, Config: c.BlackFriday} 401 ctx.Content = []byte("testContent") 402 actualRenderedMarkdown := c.mmarkRender(ctx) 403 expectedRenderedMarkdown := []byte("<p>testContent</p>\n") 404 if !bytes.Equal(actualRenderedMarkdown, expectedRenderedMarkdown) { 405 t.Errorf("Actual rendered Markdown (%s) did not match expected markdown (%s)", actualRenderedMarkdown, expectedRenderedMarkdown) 406 } 407 } 408 409 func TestExtractTOCNormalContent(t *testing.T) { 410 content := []byte("<nav>\n<ul>\nTOC<li><a href=\"#") 411 412 actualTocLessContent, actualToc := ExtractTOC(content) 413 expectedTocLess := []byte("TOC<li><a href=\"#") 414 expectedToc := []byte("<nav id=\"TableOfContents\">\n<ul>\n") 415 416 if !bytes.Equal(actualTocLessContent, expectedTocLess) { 417 t.Errorf("Actual tocless (%s) did not equal expected (%s) tocless content", actualTocLessContent, expectedTocLess) 418 } 419 420 if !bytes.Equal(actualToc, expectedToc) { 421 t.Errorf("Actual toc (%s) did not equal expected (%s) toc content", actualToc, expectedToc) 422 } 423 } 424 425 func TestExtractTOCGreaterThanSeventy(t *testing.T) { 426 content := []byte("<nav>\n<ul>\nTOC This is a very long content which will definitely be greater than seventy, I promise you that.<li><a href=\"#") 427 428 actualTocLessContent, actualToc := ExtractTOC(content) 429 //Because the start of Toc is greater than 70+startpoint of <li> content and empty TOC will be returned 430 expectedToc := []byte("") 431 432 if !bytes.Equal(actualTocLessContent, content) { 433 t.Errorf("Actual tocless (%s) did not equal expected (%s) tocless content", actualTocLessContent, content) 434 } 435 436 if !bytes.Equal(actualToc, expectedToc) { 437 t.Errorf("Actual toc (%s) did not equal expected (%s) toc content", actualToc, expectedToc) 438 } 439 } 440 441 func TestExtractNoTOC(t *testing.T) { 442 content := []byte("TOC") 443 444 actualTocLessContent, actualToc := ExtractTOC(content) 445 expectedToc := []byte("") 446 447 if !bytes.Equal(actualTocLessContent, content) { 448 t.Errorf("Actual tocless (%s) did not equal expected (%s) tocless content", actualTocLessContent, content) 449 } 450 451 if !bytes.Equal(actualToc, expectedToc) { 452 t.Errorf("Actual toc (%s) did not equal expected (%s) toc content", actualToc, expectedToc) 453 } 454 } 455 456 var totalWordsBenchmarkString = strings.Repeat("Hugo Rocks ", 200) 457 458 func TestTotalWords(t *testing.T) { 459 460 for i, this := range []struct { 461 s string 462 words int 463 }{ 464 {"Two, Words!", 2}, 465 {"Word", 1}, 466 {"", 0}, 467 {"One, Two, Three", 3}, 468 {totalWordsBenchmarkString, 400}, 469 } { 470 actualWordCount := TotalWords(this.s) 471 472 if actualWordCount != this.words { 473 t.Errorf("[%d] Actual word count (%d) for test string (%s) did not match %d", i, actualWordCount, this.s, this.words) 474 } 475 } 476 } 477 478 func BenchmarkTotalWords(b *testing.B) { 479 b.ResetTimer() 480 for i := 0; i < b.N; i++ { 481 wordCount := TotalWords(totalWordsBenchmarkString) 482 if wordCount != 400 { 483 b.Fatal("Wordcount error") 484 } 485 } 486 } 487 488 func BenchmarkTotalWordsOld(b *testing.B) { 489 b.ResetTimer() 490 for i := 0; i < b.N; i++ { 491 wordCount := totalWordsOld(totalWordsBenchmarkString) 492 if wordCount != 400 { 493 b.Fatal("Wordcount error") 494 } 495 } 496 }