github.com/graemephi/kahugo@v0.62.3-0.20211121071557-d78c0423784d/markup/goldmark/convert_test.go (about) 1 // Copyright 2019 The Hugo Authors. All rights reserved. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // http://www.apache.org/licenses/LICENSE-2.0 7 // 8 // Unless required by applicable law or agreed to in writing, software 9 // distributed under the License is distributed on an "AS IS" BASIS, 10 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11 // See the License for the specific language governing permissions and 12 // limitations under the License. 13 14 package goldmark 15 16 import ( 17 "fmt" 18 "strings" 19 "testing" 20 21 "github.com/spf13/cast" 22 23 "github.com/gohugoio/hugo/markup/goldmark/goldmark_config" 24 25 "github.com/gohugoio/hugo/markup/highlight" 26 27 "github.com/gohugoio/hugo/markup/markup_config" 28 29 "github.com/gohugoio/hugo/common/loggers" 30 31 "github.com/gohugoio/hugo/markup/converter" 32 33 qt "github.com/frankban/quicktest" 34 ) 35 36 func convert(c *qt.C, mconf markup_config.Config, content string) converter.Result { 37 p, err := Provider.New( 38 converter.ProviderConfig{ 39 MarkupConfig: mconf, 40 Logger: loggers.NewErrorLogger(), 41 }, 42 ) 43 c.Assert(err, qt.IsNil) 44 conv, err := p.New(converter.DocumentContext{DocumentID: "thedoc"}) 45 c.Assert(err, qt.IsNil) 46 b, err := conv.Convert(converter.RenderContext{RenderTOC: true, Src: []byte(content)}) 47 c.Assert(err, qt.IsNil) 48 49 return b 50 } 51 52 func TestConvert(t *testing.T) { 53 c := qt.New(t) 54 55 // Smoke test of the default configuration. 56 content := ` 57 ## Links 58 59 https://github.com/gohugoio/hugo/issues/6528 60 [Live Demo here!](https://docuapi.netlify.com/) 61 62 [I'm an inline-style link with title](https://www.google.com "Google's Homepage") 63 <https://foo.bar/> 64 https://bar.baz/ 65 <fake@example.com> 66 <mailto:fake2@example.com> 67 68 69 ## Code Fences 70 71 §§§bash 72 LINE1 73 §§§ 74 75 ## Code Fences No Lexer 76 77 §§§moo 78 LINE1 79 §§§ 80 81 ## Custom ID {#custom} 82 83 ## Auto ID 84 85 * Autolink: https://gohugo.io/ 86 * Strikethrough:~~Hi~~ Hello, world! 87 88 ## Table 89 90 | foo | bar | 91 | --- | --- | 92 | baz | bim | 93 94 ## Task Lists (default on) 95 96 - [x] Finish my changes[^1] 97 - [ ] Push my commits to GitHub 98 - [ ] Open a pull request 99 100 101 ## Smartypants (default on) 102 103 * Straight double "quotes" and single 'quotes' into “curly” quote HTML entities 104 * Dashes (“--” and “---”) into en- and em-dash entities 105 * Three consecutive dots (“...”) into an ellipsis entity 106 * Apostrophes are also converted: "That was back in the '90s, that's a long time ago" 107 108 ## Footnotes 109 110 That's some text with a footnote.[^1] 111 112 ## Definition Lists 113 114 date 115 : the datetime assigned to this page. 116 117 description 118 : the description for the content. 119 120 121 ## 神真美好 122 123 ## 神真美好 124 125 ## 神真美好 126 127 [^1]: And that's the footnote. 128 129 ` 130 131 // Code fences 132 content = strings.Replace(content, "§§§", "```", -1) 133 mconf := markup_config.Default 134 mconf.Highlight.NoClasses = false 135 mconf.Goldmark.Renderer.Unsafe = true 136 137 b := convert(c, mconf, content) 138 got := string(b.Bytes()) 139 140 fmt.Println(got) 141 142 // Links 143 c.Assert(got, qt.Contains, `<a href="https://docuapi.netlify.com/">Live Demo here!</a>`) 144 c.Assert(got, qt.Contains, `<a href="https://foo.bar/">https://foo.bar/</a>`) 145 c.Assert(got, qt.Contains, `<a href="https://bar.baz/">https://bar.baz/</a>`) 146 c.Assert(got, qt.Contains, `<a href="mailto:fake@example.com">fake@example.com</a>`) 147 c.Assert(got, qt.Contains, `<a href="mailto:fake2@example.com">mailto:fake2@example.com</a></p>`) 148 149 // Header IDs 150 c.Assert(got, qt.Contains, `<h2 id="custom">Custom ID</h2>`, qt.Commentf(got)) 151 c.Assert(got, qt.Contains, `<h2 id="auto-id">Auto ID</h2>`, qt.Commentf(got)) 152 c.Assert(got, qt.Contains, `<h2 id="神真美好">神真美好</h2>`, qt.Commentf(got)) 153 c.Assert(got, qt.Contains, `<h2 id="神真美好-1">神真美好</h2>`, qt.Commentf(got)) 154 c.Assert(got, qt.Contains, `<h2 id="神真美好-2">神真美好</h2>`, qt.Commentf(got)) 155 156 // Code fences 157 c.Assert(got, qt.Contains, "<div class=\"highlight\"><pre tabindex=\"0\" class=\"chroma\"><code class=\"language-bash\" data-lang=\"bash\">LINE1\n</code></pre></div>") 158 c.Assert(got, qt.Contains, "Code Fences No Lexer</h2>\n<pre tabindex=\"0\"><code class=\"language-moo\" data-lang=\"moo\">LINE1\n</code></pre>") 159 160 // Extensions 161 c.Assert(got, qt.Contains, `Autolink: <a href="https://gohugo.io/">https://gohugo.io/</a>`) 162 c.Assert(got, qt.Contains, `Strikethrough:<del>Hi</del> Hello, world`) 163 c.Assert(got, qt.Contains, `<th>foo</th>`) 164 c.Assert(got, qt.Contains, `<li><input disabled="" type="checkbox"> Push my commits to GitHub</li>`) 165 166 c.Assert(got, qt.Contains, `Straight double “quotes” and single ‘quotes’`) 167 c.Assert(got, qt.Contains, `Dashes (“–” and “—”) `) 168 c.Assert(got, qt.Contains, `Three consecutive dots (“…”)`) 169 c.Assert(got, qt.Contains, `“That was back in the ’90s, that’s a long time ago”`) 170 c.Assert(got, qt.Contains, `footnote.<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup>`) 171 c.Assert(got, qt.Contains, `<section class="footnotes" role="doc-endnotes">`) 172 c.Assert(got, qt.Contains, `<dt>date</dt>`) 173 174 toc, ok := b.(converter.TableOfContentsProvider) 175 c.Assert(ok, qt.Equals, true) 176 tocHTML := toc.TableOfContents().ToHTML(1, 2, false) 177 c.Assert(tocHTML, qt.Contains, "TableOfContents") 178 } 179 180 func TestConvertAutoIDAsciiOnly(t *testing.T) { 181 c := qt.New(t) 182 183 content := ` 184 ## God is Good: 神真美好 185 ` 186 mconf := markup_config.Default 187 mconf.Goldmark.Parser.AutoHeadingIDType = goldmark_config.AutoHeadingIDTypeGitHubAscii 188 b := convert(c, mconf, content) 189 got := string(b.Bytes()) 190 191 c.Assert(got, qt.Contains, "<h2 id=\"god-is-good-\">") 192 } 193 194 func TestConvertAutoIDBlackfriday(t *testing.T) { 195 c := qt.New(t) 196 197 content := ` 198 ## Let's try this, shall we? 199 200 ` 201 mconf := markup_config.Default 202 mconf.Goldmark.Parser.AutoHeadingIDType = goldmark_config.AutoHeadingIDTypeBlackfriday 203 b := convert(c, mconf, content) 204 got := string(b.Bytes()) 205 206 c.Assert(got, qt.Contains, "<h2 id=\"let-s-try-this-shall-we\">") 207 } 208 209 func TestConvertAttributes(t *testing.T) { 210 c := qt.New(t) 211 212 withBlockAttributes := func(conf *markup_config.Config) { 213 conf.Goldmark.Parser.Attribute.Block = true 214 conf.Goldmark.Parser.Attribute.Title = false 215 } 216 217 withTitleAndBlockAttributes := func(conf *markup_config.Config) { 218 conf.Goldmark.Parser.Attribute.Block = true 219 conf.Goldmark.Parser.Attribute.Title = true 220 } 221 222 for _, test := range []struct { 223 name string 224 withConfig func(conf *markup_config.Config) 225 input string 226 expect interface{} 227 }{ 228 { 229 "Title", 230 nil, 231 "## heading {#id .className attrName=attrValue class=\"class1 class2\"}", 232 "<h2 id=\"id\" class=\"className class1 class2\" attrName=\"attrValue\">heading</h2>\n", 233 }, 234 { 235 "Blockquote", 236 withBlockAttributes, 237 "> foo\n> bar\n{#id .className attrName=attrValue class=\"class1 class2\"}\n", 238 "<blockquote id=\"id\" class=\"className class1 class2\"><p>foo\nbar</p>\n</blockquote>\n", 239 }, 240 /*{ 241 // TODO(bep) this needs an upstream fix, see https://github.com/yuin/goldmark/issues/195 242 "Code block, CodeFences=false", 243 func(conf *markup_config.Config) { 244 withBlockAttributes(conf) 245 conf.Highlight.CodeFences = false 246 }, 247 "```bash\necho 'foo';\n```\n{.myclass}", 248 "TODO", 249 },*/ 250 { 251 "Code block, CodeFences=true", 252 func(conf *markup_config.Config) { 253 withBlockAttributes(conf) 254 conf.Highlight.CodeFences = true 255 }, 256 "```bash {.myclass id=\"myid\"}\necho 'foo';\n````\n", 257 "<div class=\"highlight myclass\" id=\"myid\"><pre style", 258 }, 259 { 260 "Code block, CodeFences=true,linenos=table", 261 func(conf *markup_config.Config) { 262 withBlockAttributes(conf) 263 conf.Highlight.CodeFences = true 264 }, 265 "```bash {linenos=table .myclass id=\"myid\"}\necho 'foo';\n````\n{ .adfadf }", 266 []string{"div class=\"highlight myclass\" id=\"myid\"><div s", 267 "table style"}, 268 }, 269 { 270 "Paragraph", 271 withBlockAttributes, 272 "\nHi there.\n{.myclass }", 273 "<p class=\"myclass\">Hi there.</p>\n", 274 }, 275 { 276 "Ordered list", 277 withBlockAttributes, 278 "\n1. First\n2. Second\n{.myclass }", 279 "<ol class=\"myclass\">\n<li>First</li>\n<li>Second</li>\n</ol>\n", 280 }, 281 { 282 "Unordered list", 283 withBlockAttributes, 284 "\n* First\n* Second\n{.myclass }", 285 "<ul class=\"myclass\">\n<li>First</li>\n<li>Second</li>\n</ul>\n", 286 }, 287 { 288 "Unordered list, indented", 289 withBlockAttributes, 290 `* Fruit 291 * Apple 292 * Orange 293 * Banana 294 {.fruits} 295 * Dairy 296 * Milk 297 * Cheese 298 {.dairies} 299 {.list}`, 300 []string{"<ul class=\"list\">\n<li>Fruit\n<ul class=\"fruits\">", "<li>Dairy\n<ul class=\"dairies\">"}, 301 }, 302 { 303 "Table", 304 withBlockAttributes, 305 `| A | B | 306 | ------------- |:-------------:| -----:| 307 | AV | BV | 308 {.myclass }`, 309 "<table class=\"myclass\">\n<thead>", 310 }, 311 { 312 "Title and Blockquote", 313 withTitleAndBlockAttributes, 314 "## heading {#id .className attrName=attrValue class=\"class1 class2\"}\n> foo\n> bar\n{.myclass}", 315 "<h2 id=\"id\" class=\"className class1 class2\" attrName=\"attrValue\">heading</h2>\n<blockquote class=\"myclass\"><p>foo\nbar</p>\n</blockquote>\n", 316 }, 317 } { 318 c.Run(test.name, func(c *qt.C) { 319 mconf := markup_config.Default 320 if test.withConfig != nil { 321 test.withConfig(&mconf) 322 } 323 b := convert(c, mconf, test.input) 324 got := string(b.Bytes()) 325 326 for _, s := range cast.ToStringSlice(test.expect) { 327 c.Assert(got, qt.Contains, s) 328 } 329 330 }) 331 } 332 333 } 334 335 func TestConvertIssues(t *testing.T) { 336 c := qt.New(t) 337 338 // https://github.com/gohugoio/hugo/issues/7619 339 c.Run("Hyphen in HTML attributes", func(c *qt.C) { 340 mconf := markup_config.Default 341 mconf.Goldmark.Renderer.Unsafe = true 342 input := `<custom-element> 343 <div>This will be "slotted" into the custom element.</div> 344 </custom-element> 345 ` 346 347 b := convert(c, mconf, input) 348 got := string(b.Bytes()) 349 350 c.Assert(got, qt.Contains, "<custom-element>\n <div>This will be \"slotted\" into the custom element.</div>\n</custom-element>\n") 351 }) 352 } 353 354 func TestCodeFence(t *testing.T) { 355 c := qt.New(t) 356 357 lines := `LINE1 358 LINE2 359 LINE3 360 LINE4 361 LINE5 362 ` 363 364 convertForConfig := func(c *qt.C, conf highlight.Config, code, language string) string { 365 mconf := markup_config.Default 366 mconf.Highlight = conf 367 368 p, err := Provider.New( 369 converter.ProviderConfig{ 370 MarkupConfig: mconf, 371 Logger: loggers.NewErrorLogger(), 372 }, 373 ) 374 375 content := "```" + language + "\n" + code + "\n```" 376 377 c.Assert(err, qt.IsNil) 378 conv, err := p.New(converter.DocumentContext{}) 379 c.Assert(err, qt.IsNil) 380 b, err := conv.Convert(converter.RenderContext{Src: []byte(content)}) 381 c.Assert(err, qt.IsNil) 382 383 return string(b.Bytes()) 384 } 385 386 c.Run("Basic", func(c *qt.C) { 387 cfg := highlight.DefaultConfig 388 cfg.NoClasses = false 389 390 result := convertForConfig(c, cfg, `echo "Hugo Rocks!"`, "bash") 391 // TODO(bep) there is a whitespace mismatch (\n) between this and the highlight template func. 392 c.Assert(result, qt.Equals, `<div class="highlight"><pre tabindex="0" class="chroma"><code class="language-bash" data-lang="bash"><span class="nb">echo</span> <span class="s2">"Hugo Rocks!"</span> 393 </code></pre></div>`) 394 result = convertForConfig(c, cfg, `echo "Hugo Rocks!"`, "unknown") 395 c.Assert(result, qt.Equals, "<pre tabindex=\"0\"><code class=\"language-unknown\" data-lang=\"unknown\">echo "Hugo Rocks!"\n</code></pre>") 396 }) 397 398 c.Run("Highlight lines, default config", func(c *qt.C) { 399 cfg := highlight.DefaultConfig 400 cfg.NoClasses = false 401 402 result := convertForConfig(c, cfg, lines, `bash {linenos=table,hl_lines=[2 "4-5"],linenostart=3}`) 403 c.Assert(result, qt.Contains, "<div class=\"highlight\"><div class=\"chroma\">\n<table class=\"lntable\"><tr><td class=\"lntd\">\n<pre tabindex=\"0\" class=\"chroma\"><code><span class") 404 c.Assert(result, qt.Contains, "<span class=\"hl\"><span class=\"lnt\">4") 405 406 result = convertForConfig(c, cfg, lines, "bash {linenos=inline,hl_lines=[2]}") 407 c.Assert(result, qt.Contains, "<span class=\"ln\">2</span>LINE2\n</span>") 408 c.Assert(result, qt.Not(qt.Contains), "<table") 409 410 result = convertForConfig(c, cfg, lines, "bash {linenos=true,hl_lines=[2]}") 411 c.Assert(result, qt.Contains, "<table") 412 c.Assert(result, qt.Contains, "<span class=\"hl\"><span class=\"lnt\">2\n</span>") 413 }) 414 415 c.Run("Highlight lines, linenumbers default on", func(c *qt.C) { 416 cfg := highlight.DefaultConfig 417 cfg.NoClasses = false 418 cfg.LineNos = true 419 420 result := convertForConfig(c, cfg, lines, "bash") 421 c.Assert(result, qt.Contains, "<span class=\"lnt\">2\n</span>") 422 423 result = convertForConfig(c, cfg, lines, "bash {linenos=false,hl_lines=[2]}") 424 c.Assert(result, qt.Not(qt.Contains), "class=\"lnt\"") 425 }) 426 427 c.Run("Highlight lines, linenumbers default on, linenumbers in table default off", func(c *qt.C) { 428 cfg := highlight.DefaultConfig 429 cfg.NoClasses = false 430 cfg.LineNos = true 431 cfg.LineNumbersInTable = false 432 433 result := convertForConfig(c, cfg, lines, "bash") 434 c.Assert(result, qt.Contains, "<span class=\"ln\">2</span>LINE2\n<") 435 result = convertForConfig(c, cfg, lines, "bash {linenos=table}") 436 c.Assert(result, qt.Contains, "<span class=\"lnt\">1\n</span>") 437 }) 438 439 c.Run("No language", func(c *qt.C) { 440 cfg := highlight.DefaultConfig 441 cfg.NoClasses = false 442 cfg.LineNos = true 443 cfg.LineNumbersInTable = false 444 445 result := convertForConfig(c, cfg, lines, "") 446 c.Assert(result, qt.Contains, "<pre tabindex=\"0\"><code>LINE1\n") 447 }) 448 449 c.Run("No language, guess syntax", func(c *qt.C) { 450 cfg := highlight.DefaultConfig 451 cfg.NoClasses = false 452 cfg.GuessSyntax = true 453 cfg.LineNos = true 454 cfg.LineNumbersInTable = false 455 456 result := convertForConfig(c, cfg, lines, "") 457 c.Assert(result, qt.Contains, "<span class=\"ln\">2</span>LINE2\n<") 458 }) 459 }