github.com/gitbundle/modules@v0.0.0-20231025071548-85b91c5c3b01/markup/markdown/markdown_test.go (about) 1 // Copyright 2023 The GitBundle Inc. All rights reserved. 2 // Copyright 2017 The Gitea Authors. All rights reserved. 3 // Use of this source code is governed by a MIT-style 4 // license that can be found in the LICENSE file. 5 6 package markdown_test 7 8 import ( 9 "context" 10 "strings" 11 "testing" 12 13 "github.com/gitbundle/modules/git" 14 "github.com/gitbundle/modules/log" 15 "github.com/gitbundle/modules/markup" 16 . "github.com/gitbundle/modules/markup/markdown" 17 "github.com/gitbundle/modules/setting" 18 "github.com/gitbundle/modules/util" 19 20 "github.com/stretchr/testify/assert" 21 ) 22 23 const ( 24 AppURL = "http://localhost:3000/" 25 Repo = "gogits/gogs" 26 AppSubURL = AppURL + Repo + "/" 27 ) 28 29 // these values should match the Repo const above 30 var localMetas = map[string]string{ 31 "user": "gogits", 32 "repo": "gogs", 33 "repoPath": "../../../integrations/gitea-repositories-meta/user13/repo11.git/", 34 } 35 36 func TestMain(m *testing.M) { 37 setting.LoadAllowEmpty() 38 if err := git.InitSimple(context.Background()); err != nil { 39 log.Fatal("git init failed, err: %v", err) 40 } 41 } 42 43 func TestRender_StandardLinks(t *testing.T) { 44 setting.AppURL = AppURL 45 setting.AppSubURL = AppSubURL 46 47 test := func(input, expected, expectedWiki string) { 48 buffer, err := RenderString(&markup.RenderContext{ 49 URLPrefix: setting.AppSubURL, 50 }, input) 51 assert.NoError(t, err) 52 assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer)) 53 54 buffer, err = RenderString(&markup.RenderContext{ 55 URLPrefix: setting.AppSubURL, 56 IsWiki: true, 57 }, input) 58 assert.NoError(t, err) 59 assert.Equal(t, strings.TrimSpace(expectedWiki), strings.TrimSpace(buffer)) 60 } 61 62 googleRendered := `<p><a href="https://google.com/" rel="nofollow">https://google.com/</a></p>` 63 test("<https://google.com/>", googleRendered, googleRendered) 64 65 lnk := util.URLJoin(AppSubURL, "WikiPage") 66 lnkWiki := util.URLJoin(AppSubURL, "wiki", "WikiPage") 67 test("[WikiPage](WikiPage)", 68 `<p><a href="`+lnk+`" rel="nofollow">WikiPage</a></p>`, 69 `<p><a href="`+lnkWiki+`" rel="nofollow">WikiPage</a></p>`) 70 } 71 72 func TestMisc_IsMarkdownFile(t *testing.T) { 73 setting.Markdown.FileExtensions = []string{".md", ".markdown", ".mdown", ".mkd"} 74 trueTestCases := []string{ 75 "test.md", 76 "wow.MARKDOWN", 77 "LOL.mDoWn", 78 } 79 falseTestCases := []string{ 80 "test", 81 "abcdefg", 82 "abcdefghijklmnopqrstuvwxyz", 83 "test.md.test", 84 } 85 86 for _, testCase := range trueTestCases { 87 assert.True(t, IsMarkdownFile(testCase)) 88 } 89 for _, testCase := range falseTestCases { 90 assert.False(t, IsMarkdownFile(testCase)) 91 } 92 } 93 94 func TestRender_Images(t *testing.T) { 95 setting.AppURL = AppURL 96 setting.AppSubURL = AppSubURL 97 98 test := func(input, expected string) { 99 buffer, err := RenderString(&markup.RenderContext{ 100 URLPrefix: setting.AppSubURL, 101 }, input) 102 assert.NoError(t, err) 103 assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer)) 104 } 105 106 url := "../../.images/src/02/train.jpg" 107 title := "Train" 108 href := "https://gitea.io" 109 result := util.URLJoin(AppSubURL, url) 110 // hint: With Markdown v2.5.2, there is a new syntax: [link](URL){:target="_blank"} , but we do not support it now 111 112 test( 113 "!["+title+"]("+url+")", 114 `<p><a href="`+result+`" target="_blank" rel="nofollow noopener"><img src="`+result+`" alt="`+title+`"/></a></p>`) 115 116 test( 117 "[["+title+"|"+url+"]]", 118 `<p><a href="`+result+`" rel="nofollow"><img src="`+result+`" title="`+title+`" alt="`+title+`"/></a></p>`) 119 test( 120 "[!["+title+"]("+url+")]("+href+")", 121 `<p><a href="`+href+`" rel="nofollow"><img src="`+result+`" alt="`+title+`"/></a></p>`) 122 123 url = "/../../.images/src/02/train.jpg" 124 test( 125 "!["+title+"]("+url+")", 126 `<p><a href="`+result+`" target="_blank" rel="nofollow noopener"><img src="`+result+`" alt="`+title+`"/></a></p>`) 127 128 test( 129 "[["+title+"|"+url+"]]", 130 `<p><a href="`+result+`" rel="nofollow"><img src="`+result+`" title="`+title+`" alt="`+title+`"/></a></p>`) 131 test( 132 "[!["+title+"]("+url+")]("+href+")", 133 `<p><a href="`+href+`" rel="nofollow"><img src="`+result+`" alt="`+title+`"/></a></p>`) 134 } 135 136 func testAnswers(baseURLContent, baseURLImages string) []string { 137 return []string{ 138 `<p>Wiki! Enjoy :)</p> 139 <ul> 140 <li><a href="` + baseURLContent + `/Links" rel="nofollow">Links, Language bindings, Engine bindings</a></li> 141 <li><a href="` + baseURLContent + `/Tips" rel="nofollow">Tips</a></li> 142 </ul> 143 <p>See commit <a href="http://localhost:3000/gogits/gogs/commit/65f1bf27bc" rel="nofollow"><code>65f1bf27bc</code></a></p> 144 <p>Ideas and codes</p> 145 <ul> 146 <li>Bezier widget (by <a href="` + AppURL + `r-lyeh" rel="nofollow">@r-lyeh</a>) <a href="http://localhost:3000/ocornut/imgui/issues/786" class="ref-issue" rel="nofollow">ocornut/imgui#786</a></li> 147 <li>Bezier widget (by <a href="` + AppURL + `r-lyeh" rel="nofollow">@r-lyeh</a>) <a href="http://localhost:3000/gogits/gogs/issues/786" class="ref-issue" rel="nofollow">#786</a></li> 148 <li>Node graph editors <a href="https://github.com/ocornut/imgui/issues/306" rel="nofollow">https://github.com/ocornut/imgui/issues/306</a></li> 149 <li><a href="` + baseURLContent + `/memory_editor_example" rel="nofollow">Memory Editor</a></li> 150 <li><a href="` + baseURLContent + `/plot_var_example" rel="nofollow">Plot var helper</a></li> 151 </ul> 152 `, 153 `<h2 id="user-content-what-is-wine-staging">What is Wine Staging?</h2> 154 <p><strong>Wine Staging</strong> on website <a href="http://wine-staging.com" rel="nofollow">wine-staging.com</a>.</p> 155 <h2 id="user-content-quick-links">Quick Links</h2> 156 <p>Here are some links to the most important topics. You can find the full list of pages at the sidebar.</p> 157 <table> 158 <thead> 159 <tr> 160 <th><a href="` + baseURLImages + `/images/icon-install.png" rel="nofollow"><img src="` + baseURLImages + `/images/icon-install.png" title="icon-install.png" alt="images/icon-install.png"/></a></th> 161 <th><a href="` + baseURLContent + `/Installation" rel="nofollow">Installation</a></th> 162 </tr> 163 </thead> 164 <tbody> 165 <tr> 166 <td><a href="` + baseURLImages + `/images/icon-usage.png" rel="nofollow"><img src="` + baseURLImages + `/images/icon-usage.png" title="icon-usage.png" alt="images/icon-usage.png"/></a></td> 167 <td><a href="` + baseURLContent + `/Usage" rel="nofollow">Usage</a></td> 168 </tr> 169 </tbody> 170 </table> 171 `, 172 `<p><a href="http://www.excelsiorjet.com/" rel="nofollow">Excelsior JET</a> allows you to create native executables for Windows, Linux and Mac OS X.</p> 173 <ol> 174 <li><a href="https://github.com/libgdx/libgdx/wiki/Gradle-on-the-Commandline#packaging-for-the-desktop" rel="nofollow">Package your libGDX application</a><br/> 175 <a href="` + baseURLImages + `/images/1.png" rel="nofollow"><img src="` + baseURLImages + `/images/1.png" title="1.png" alt="images/1.png"/></a></li> 176 <li>Perform a test run by hitting the Run! button.<br/> 177 <a href="` + baseURLImages + `/images/2.png" rel="nofollow"><img src="` + baseURLImages + `/images/2.png" title="2.png" alt="images/2.png"/></a></li> 178 </ol> 179 <h2 id="user-content-custom-id">More tests</h2> 180 <p>(from <a href="https://www.markdownguide.org/extended-syntax/" rel="nofollow">https://www.markdownguide.org/extended-syntax/</a>)</p> 181 <h3 id="user-content-checkboxes">Checkboxes</h3> 182 <ul> 183 <li class="task-list-item"><input type="checkbox" disabled="" data-source-position="434"/>unchecked</li> 184 <li class="task-list-item"><input type="checkbox" disabled="" data-source-position="450" checked=""/>checked</li> 185 <li class="task-list-item"><input type="checkbox" disabled="" data-source-position="464"/>still unchecked</li> 186 </ul> 187 <h3 id="user-content-definition-list">Definition list</h3> 188 <dl> 189 <dt>First Term</dt> 190 <dd>This is the definition of the first term.</dd> 191 <dt>Second Term</dt> 192 <dd>This is one definition of the second term.</dd> 193 <dd>This is another definition of the second term.</dd> 194 </dl> 195 <h3 id="user-content-footnotes">Footnotes</h3> 196 <p>Here is a simple footnote,<sup id="fnref:user-content-1"><a href="#fn:user-content-1" rel="nofollow">1</a></sup> and here is a longer one.<sup id="fnref:user-content-bignote"><a href="#fn:user-content-bignote" rel="nofollow">2</a></sup></p> 197 <div> 198 <hr/> 199 <ol> 200 <li id="fn:user-content-1"> 201 <p>This is the first footnote. <a href="#fnref:user-content-1" rel="nofollow">↩︎</a></p> 202 </li> 203 <li id="fn:user-content-bignote"> 204 <p>Here is one with multiple paragraphs and code.</p> 205 <p>Indent paragraphs to include them in the footnote.</p> 206 <p><code>{ my code }</code></p> 207 <p>Add as many paragraphs as you like. <a href="#fnref:user-content-bignote" rel="nofollow">↩︎</a></p> 208 </li> 209 </ol> 210 </div> 211 `, `<ul> 212 <li class="task-list-item"><input type="checkbox" disabled="" data-source-position="3"/> If you want to rebase/retry this PR, click this checkbox.</li> 213 </ul> 214 <hr/> 215 <p>This PR has been generated by <a href="https://github.com/renovatebot/renovate" rel="nofollow">Renovate Bot</a>.</p> 216 `, 217 } 218 } 219 220 // Test cases without ambiguous links 221 var sameCases = []string{ 222 // dear imgui wiki markdown extract: special wiki syntax 223 `Wiki! Enjoy :) 224 - [[Links, Language bindings, Engine bindings|Links]] 225 - [[Tips]] 226 227 See commit 65f1bf27bc 228 229 Ideas and codes 230 231 - Bezier widget (by @r-lyeh) ` + AppURL + `ocornut/imgui/issues/786 232 - Bezier widget (by @r-lyeh) ` + AppURL + `gogits/gogs/issues/786 233 - Node graph editors https://github.com/ocornut/imgui/issues/306 234 - [[Memory Editor|memory_editor_example]] 235 - [[Plot var helper|plot_var_example]]`, 236 // wine-staging wiki home extract: tables, special wiki syntax, images 237 `## What is Wine Staging? 238 **Wine Staging** on website [wine-staging.com](http://wine-staging.com). 239 240 ## Quick Links 241 Here are some links to the most important topics. You can find the full list of pages at the sidebar. 242 243 | [[images/icon-install.png]] | [[Installation]] | 244 |--------------------------------|----------------------------------------------------------| 245 | [[images/icon-usage.png]] | [[Usage]] | 246 `, 247 // libgdx wiki page: inline images with special syntax 248 `[Excelsior JET](http://www.excelsiorjet.com/) allows you to create native executables for Windows, Linux and Mac OS X. 249 250 1. [Package your libGDX application](https://github.com/libgdx/libgdx/wiki/Gradle-on-the-Commandline#packaging-for-the-desktop) 251 [[images/1.png]] 252 2. Perform a test run by hitting the Run! button. 253 [[images/2.png]] 254 255 ## More tests {#custom-id} 256 257 (from https://www.markdownguide.org/extended-syntax/) 258 259 ### Checkboxes 260 261 - [ ] unchecked 262 - [x] checked 263 - [ ] still unchecked 264 265 ### Definition list 266 267 First Term 268 : This is the definition of the first term. 269 270 Second Term 271 : This is one definition of the second term. 272 : This is another definition of the second term. 273 274 ### Footnotes 275 276 Here is a simple footnote,[^1] and here is a longer one.[^bignote] 277 278 [^1]: This is the first footnote. 279 280 [^bignote]: Here is one with multiple paragraphs and code. 281 282 Indent paragraphs to include them in the footnote. 283 284 ` + "`{ my code }`" + ` 285 286 Add as many paragraphs as you like. 287 `, 288 ` 289 - [ ] <!-- rebase-check --> If you want to rebase/retry this PR, click this checkbox. 290 291 --- 292 293 This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate). 294 295 <!-- test-comment -->`, 296 } 297 298 func TestTotal_RenderWiki(t *testing.T) { 299 setting.AppURL = AppURL 300 setting.AppSubURL = AppSubURL 301 302 answers := testAnswers(util.URLJoin(AppSubURL, "wiki/"), util.URLJoin(AppSubURL, "wiki", "raw/")) 303 304 for i := 0; i < len(sameCases); i++ { 305 line, err := RenderString(&markup.RenderContext{ 306 Ctx: git.DefaultContext, 307 URLPrefix: AppSubURL, 308 Metas: localMetas, 309 IsWiki: true, 310 }, sameCases[i]) 311 assert.NoError(t, err) 312 assert.Equal(t, answers[i], line) 313 } 314 315 testCases := []string{ 316 // Guard wiki sidebar: special syntax 317 `[[Guardfile-DSL / Configuring-Guard|Guardfile-DSL---Configuring-Guard]]`, 318 // rendered 319 `<p><a href="` + AppSubURL + `wiki/Guardfile-DSL---Configuring-Guard" rel="nofollow">Guardfile-DSL / Configuring-Guard</a></p> 320 `, 321 // special syntax 322 `[[Name|Link]]`, 323 // rendered 324 `<p><a href="` + AppSubURL + `wiki/Link" rel="nofollow">Name</a></p> 325 `, 326 } 327 328 for i := 0; i < len(testCases); i += 2 { 329 line, err := RenderString(&markup.RenderContext{ 330 URLPrefix: AppSubURL, 331 IsWiki: true, 332 }, testCases[i]) 333 assert.NoError(t, err) 334 assert.Equal(t, testCases[i+1], line) 335 } 336 } 337 338 func TestTotal_RenderString(t *testing.T) { 339 setting.AppURL = AppURL 340 setting.AppSubURL = AppSubURL 341 342 answers := testAnswers(util.URLJoin(AppSubURL, "src", "master/"), util.URLJoin(AppSubURL, "raw", "master/")) 343 344 for i := 0; i < len(sameCases); i++ { 345 line, err := RenderString(&markup.RenderContext{ 346 Ctx: git.DefaultContext, 347 URLPrefix: util.URLJoin(AppSubURL, "src", "master/"), 348 Metas: localMetas, 349 }, sameCases[i]) 350 assert.NoError(t, err) 351 assert.Equal(t, answers[i], line) 352 } 353 354 testCases := []string{} 355 356 for i := 0; i < len(testCases); i += 2 { 357 line, err := RenderString(&markup.RenderContext{ 358 URLPrefix: AppSubURL, 359 }, testCases[i]) 360 assert.NoError(t, err) 361 assert.Equal(t, testCases[i+1], line) 362 } 363 } 364 365 func TestRender_RenderParagraphs(t *testing.T) { 366 test := func(t *testing.T, str string, cnt int) { 367 res, err := RenderRawString(&markup.RenderContext{}, str) 368 assert.NoError(t, err) 369 assert.Equal(t, cnt, strings.Count(res, "<p"), "Rendered result for unix should have %d paragraph(s) but has %d:\n%s\n", cnt, strings.Count(res, "<p"), res) 370 371 mac := strings.ReplaceAll(str, "\n", "\r") 372 res, err = RenderRawString(&markup.RenderContext{}, mac) 373 assert.NoError(t, err) 374 assert.Equal(t, cnt, strings.Count(res, "<p"), "Rendered result for mac should have %d paragraph(s) but has %d:\n%s\n", cnt, strings.Count(res, "<p"), res) 375 376 dos := strings.ReplaceAll(str, "\n", "\r\n") 377 res, err = RenderRawString(&markup.RenderContext{}, dos) 378 assert.NoError(t, err) 379 assert.Equal(t, cnt, strings.Count(res, "<p"), "Rendered result for windows should have %d paragraph(s) but has %d:\n%s\n", cnt, strings.Count(res, "<p"), res) 380 } 381 382 test(t, "\nOne\nTwo\nThree", 1) 383 test(t, "\n\nOne\nTwo\nThree", 1) 384 test(t, "\n\nOne\nTwo\nThree\n\n\n", 1) 385 test(t, "A\n\nB\nC\n", 2) 386 test(t, "A\n\n\nB\nC\n", 2) 387 } 388 389 func TestMarkdownRenderRaw(t *testing.T) { 390 testcases := [][]byte{ 391 { // clusterfuzz_testcase_minimized_fuzz_markdown_render_raw_6267570554535936 392 0x2a, 0x20, 0x2d, 0x0a, 0x09, 0x20, 0x60, 0x5b, 0x0a, 0x09, 0x20, 0x60, 393 0x5b, 394 }, 395 { // clusterfuzz_testcase_minimized_fuzz_markdown_render_raw_6278827345051648 396 0x2d, 0x20, 0x2d, 0x0d, 0x09, 0x60, 0x0d, 0x09, 0x60, 397 }, 398 { // clusterfuzz_testcase_minimized_fuzz_markdown_render_raw_6016973788020736[] = { 399 0x7b, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x3d, 0x35, 0x7d, 0x0a, 0x3d, 400 }, 401 } 402 403 for _, testcase := range testcases { 404 log.Info("Test markdown render error with fuzzy data: %x, the following errors can be recovered", testcase) 405 _, err := RenderRawString(&markup.RenderContext{}, string(testcase)) 406 assert.NoError(t, err) 407 } 408 } 409 410 func TestRenderSiblingImages_Issue12925(t *testing.T) { 411 testcase := `![image1](/image1) 412 ![image2](/image2) 413 ` 414 expected := `<p><a href="/image1" target="_blank" rel="nofollow noopener"><img src="/image1" alt="image1"></a><br> 415 <a href="/image2" target="_blank" rel="nofollow noopener"><img src="/image2" alt="image2"></a></p> 416 ` 417 res, err := RenderRawString(&markup.RenderContext{}, testcase) 418 assert.NoError(t, err) 419 assert.Equal(t, expected, res) 420 } 421 422 func TestRenderEmojiInLinks_Issue12331(t *testing.T) { 423 testcase := `[Link with emoji :moon: in text](https://gitea.io)` 424 expected := `<p><a href="https://gitea.io" rel="nofollow">Link with emoji <span class="emoji" aria-label="waxing gibbous moon">🌔</span> in text</a></p> 425 ` 426 res, err := RenderString(&markup.RenderContext{}, testcase) 427 assert.NoError(t, err) 428 assert.Equal(t, expected, res) 429 }