gitea源码

markdown_test.go 20KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550
  1. // Copyright 2017 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package markdown_test
  4. import (
  5. "context"
  6. "html/template"
  7. "strings"
  8. "testing"
  9. "code.gitea.io/gitea/modules/log"
  10. "code.gitea.io/gitea/modules/markup"
  11. "code.gitea.io/gitea/modules/markup/markdown"
  12. "code.gitea.io/gitea/modules/setting"
  13. "code.gitea.io/gitea/modules/test"
  14. "code.gitea.io/gitea/modules/util"
  15. "github.com/stretchr/testify/assert"
  16. )
  17. const (
  18. AppURL = "http://localhost:3000/"
  19. testRepoOwnerName = "user13"
  20. testRepoName = "repo11"
  21. FullURL = AppURL + testRepoOwnerName + "/" + testRepoName + "/"
  22. )
  23. // these values should match the const above
  24. var localMetas = map[string]string{
  25. "user": testRepoOwnerName,
  26. "repo": testRepoName,
  27. }
  28. func TestRender_StandardLinks(t *testing.T) {
  29. test := func(input, expected string) {
  30. buffer, err := markdown.RenderString(markup.NewTestRenderContext(), input)
  31. assert.NoError(t, err)
  32. assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(string(buffer)))
  33. }
  34. googleRendered := `<p><a href="https://google.com/" rel="nofollow">https://google.com/</a></p>`
  35. test("<https://google.com/>", googleRendered)
  36. test("[Link](Link)", `<p><a href="/Link" rel="nofollow">Link</a></p>`)
  37. }
  38. func TestRender_Images(t *testing.T) {
  39. setting.AppURL = AppURL
  40. render := func(input, expected string) {
  41. buffer, err := markdown.RenderString(markup.NewTestRenderContext(FullURL), input)
  42. assert.NoError(t, err)
  43. assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(string(buffer)))
  44. }
  45. url := "../../.images/src/02/train.jpg"
  46. title := "Train"
  47. href := "https://gitea.io"
  48. result := util.URLJoin(FullURL, url)
  49. // hint: With Markdown v2.5.2, there is a new syntax: [link](URL){:target="_blank"} , but we do not support it now
  50. render(
  51. "!["+title+"]("+url+")",
  52. `<p><a href="`+result+`" target="_blank" rel="nofollow noopener"><img src="`+result+`" alt="`+title+`"/></a></p>`)
  53. render(
  54. "[["+title+"|"+url+"]]",
  55. `<p><a href="`+result+`" rel="nofollow"><img src="`+result+`" title="`+title+`" alt="`+title+`"/></a></p>`)
  56. render(
  57. "[!["+title+"]("+url+")]("+href+")",
  58. `<p><a href="`+href+`" rel="nofollow"><img src="`+result+`" alt="`+title+`"/></a></p>`)
  59. render(
  60. "!["+title+"]("+url+")",
  61. `<p><a href="`+result+`" target="_blank" rel="nofollow noopener"><img src="`+result+`" alt="`+title+`"/></a></p>`)
  62. render(
  63. "[["+title+"|"+url+"]]",
  64. `<p><a href="`+result+`" rel="nofollow"><img src="`+result+`" title="`+title+`" alt="`+title+`"/></a></p>`)
  65. render(
  66. "[!["+title+"]("+url+")]("+href+")",
  67. `<p><a href="`+href+`" rel="nofollow"><img src="`+result+`" alt="`+title+`"/></a></p>`)
  68. defer test.MockVariableValue(&markup.RenderBehaviorForTesting.DisableAdditionalAttributes, false)()
  69. render(
  70. "<a><img src='a.jpg'></a>", // by the way, empty "a" tag will be removed
  71. `<p dir="auto"><img src="http://localhost:3000/user13/repo11/a.jpg" loading="lazy"/></p>`)
  72. }
  73. func TestTotal_RenderString(t *testing.T) {
  74. defer test.MockVariableValue(&markup.RenderBehaviorForTesting.DisableAdditionalAttributes, true)()
  75. // Test cases without ambiguous links (It is not right to copy a whole file here, instead it should clearly test what is being tested)
  76. sameCases := []string{
  77. // dear imgui wiki markdown extract: special wiki syntax
  78. `Wiki! Enjoy :)
  79. - [[Links, Language bindings, Engine bindings|Links]]
  80. - [[Tips]]
  81. See commit 65f1bf27bc
  82. Ideas and codes
  83. - Bezier widget (by @r-lyeh) ` + AppURL + `ocornut/imgui/issues/786
  84. - Bezier widget (by @r-lyeh) ` + FullURL + `issues/786
  85. - Node graph editors https://github.com/ocornut/imgui/issues/306
  86. - [[Memory Editor|memory_editor_example]]
  87. - [[Plot var helper|plot_var_example]]`,
  88. // wine-staging wiki home extract: tables, special wiki syntax, images
  89. `## What is Wine Staging?
  90. **Wine Staging** on website [wine-staging.com](http://wine-staging.com).
  91. ## Quick Links
  92. Here are some links to the most important topics. You can find the full list of pages at the sidebar.
  93. | [[images/icon-install.png]] | [[Installation]] |
  94. |--------------------------------|----------------------------------------------------------|
  95. | [[images/icon-usage.png]] | [[Usage]] |
  96. `,
  97. // libgdx wiki page: inline images with special syntax
  98. `[Excelsior JET](http://www.excelsiorjet.com/) allows you to create native executables for Windows, Linux and Mac OS X.
  99. 1. [Package your libGDX application](https://github.com/libgdx/libgdx/wiki/Gradle-on-the-Commandline#packaging-for-the-desktop)
  100. [[images/1.png]]
  101. 2. Perform a test run by hitting the Run! button.
  102. [[images/2.png]]
  103. ## More tests {#custom-id}
  104. (from https://www.markdownguide.org/extended-syntax/)
  105. ### Checkboxes
  106. - [ ] unchecked
  107. - [x] checked
  108. - [ ] still unchecked
  109. ### Definition list
  110. First Term
  111. : This is the definition of the first term.
  112. Second Term
  113. : This is one definition of the second term.
  114. : This is another definition of the second term.
  115. ### Footnotes
  116. Here is a simple footnote,[^1] and here is a longer one.[^bignote]
  117. [^1]: This is the first footnote.
  118. [^bignote]: Here is one with multiple paragraphs and code.
  119. Indent paragraphs to include them in the footnote.
  120. ` + "`{ my code }`" + `
  121. Add as many paragraphs as you like.
  122. `,
  123. `
  124. - [ ] <!-- rebase-check --> If you want to rebase/retry this PR, click this checkbox.
  125. ---
  126. This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
  127. <!-- test-comment -->`,
  128. }
  129. baseURL := ""
  130. testAnswers := []string{
  131. `<p>Wiki! Enjoy :)</p>
  132. <ul>
  133. <li><a href="` + baseURL + `/Links" rel="nofollow">Links, Language bindings, Engine bindings</a></li>
  134. <li><a href="` + baseURL + `/Tips" rel="nofollow">Tips</a></li>
  135. </ul>
  136. <p>See commit <a href="/` + testRepoOwnerName + `/` + testRepoName + `/commit/65f1bf27bc" rel="nofollow"><code>65f1bf27bc</code></a></p>
  137. <p>Ideas and codes</p>
  138. <ul>
  139. <li>Bezier widget (by <a href="/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>
  140. <li>Bezier widget (by <a href="/r-lyeh" rel="nofollow">@r-lyeh</a>) <a href="` + FullURL + `issues/786" class="ref-issue" rel="nofollow">#786</a></li>
  141. <li>Node graph editors <a href="https://github.com/ocornut/imgui/issues/306" rel="nofollow">https://github.com/ocornut/imgui/issues/306</a></li>
  142. <li><a href="` + baseURL + `/memory_editor_example" rel="nofollow">Memory Editor</a></li>
  143. <li><a href="` + baseURL + `/plot_var_example" rel="nofollow">Plot var helper</a></li>
  144. </ul>
  145. `,
  146. `<h2 id="user-content-what-is-wine-staging">What is Wine Staging?</h2>
  147. <p><strong>Wine Staging</strong> on website <a href="http://wine-staging.com" rel="nofollow">wine-staging.com</a>.</p>
  148. <h2 id="user-content-quick-links">Quick Links</h2>
  149. <p>Here are some links to the most important topics. You can find the full list of pages at the sidebar.</p>
  150. <table>
  151. <thead>
  152. <tr>
  153. <th><a href="` + baseURL + `/images/icon-install.png" rel="nofollow"><img src="` + baseURL + `/images/icon-install.png" title="icon-install.png" alt="images/icon-install.png"/></a></th>
  154. <th><a href="` + baseURL + `/Installation" rel="nofollow">Installation</a></th>
  155. </tr>
  156. </thead>
  157. <tbody>
  158. <tr>
  159. <td><a href="` + baseURL + `/images/icon-usage.png" rel="nofollow"><img src="` + baseURL + `/images/icon-usage.png" title="icon-usage.png" alt="images/icon-usage.png"/></a></td>
  160. <td><a href="` + baseURL + `/Usage" rel="nofollow">Usage</a></td>
  161. </tr>
  162. </tbody>
  163. </table>
  164. `,
  165. `<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>
  166. <ol>
  167. <li><a href="https://github.com/libgdx/libgdx/wiki/Gradle-on-the-Commandline#packaging-for-the-desktop" rel="nofollow">Package your libGDX application</a>
  168. <a href="` + baseURL + `/images/1.png" rel="nofollow"><img src="` + baseURL + `/images/1.png" title="1.png" alt="images/1.png"/></a></li>
  169. <li>Perform a test run by hitting the Run! button.
  170. <a href="` + baseURL + `/images/2.png" rel="nofollow"><img src="` + baseURL + `/images/2.png" title="2.png" alt="images/2.png"/></a></li>
  171. </ol>
  172. <h2 id="user-content-custom-id">More tests</h2>
  173. <p>(from <a href="https://www.markdownguide.org/extended-syntax/" rel="nofollow">https://www.markdownguide.org/extended-syntax/</a>)</p>
  174. <h3 id="user-content-checkboxes">Checkboxes</h3>
  175. <ul>
  176. <li class="task-list-item"><input type="checkbox" disabled="" data-source-position="434"/>unchecked</li>
  177. <li class="task-list-item"><input type="checkbox" disabled="" data-source-position="450" checked=""/>checked</li>
  178. <li class="task-list-item"><input type="checkbox" disabled="" data-source-position="464"/>still unchecked</li>
  179. </ul>
  180. <h3 id="user-content-definition-list">Definition list</h3>
  181. <dl>
  182. <dt>First Term</dt>
  183. <dd>This is the definition of the first term.</dd>
  184. <dt>Second Term</dt>
  185. <dd>This is one definition of the second term.</dd>
  186. <dd>This is another definition of the second term.</dd>
  187. </dl>
  188. <h3 id="user-content-footnotes">Footnotes</h3>
  189. <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>
  190. <div>
  191. <hr/>
  192. <ol>
  193. <li id="fn:user-content-1">
  194. <p>This is the first footnote. <a href="#fnref:user-content-1" rel="nofollow">↩︎</a></p>
  195. </li>
  196. <li id="fn:user-content-bignote">
  197. <p>Here is one with multiple paragraphs and code.</p>
  198. <p>Indent paragraphs to include them in the footnote.</p>
  199. <p><code>{ my code }</code></p>
  200. <p>Add as many paragraphs as you like. <a href="#fnref:user-content-bignote" rel="nofollow">↩︎</a></p>
  201. </li>
  202. </ol>
  203. </div>
  204. `,
  205. `<ul>
  206. <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>
  207. </ul>
  208. <hr/>
  209. <p>This PR has been generated by <a href="https://github.com/renovatebot/renovate" rel="nofollow">Renovate Bot</a>.</p>
  210. `,
  211. }
  212. markup.Init(&markup.RenderHelperFuncs{
  213. IsUsernameMentionable: func(ctx context.Context, username string) bool {
  214. return username == "r-lyeh"
  215. },
  216. })
  217. for i := range sameCases {
  218. line, err := markdown.RenderString(markup.NewTestRenderContext(localMetas), sameCases[i])
  219. assert.NoError(t, err)
  220. assert.Equal(t, testAnswers[i], string(line))
  221. }
  222. }
  223. func TestRender_RenderParagraphs(t *testing.T) {
  224. test := func(t *testing.T, str string, cnt int) {
  225. res, err := markdown.RenderRawString(markup.NewTestRenderContext(), str)
  226. assert.NoError(t, err)
  227. 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)
  228. mac := strings.ReplaceAll(str, "\n", "\r")
  229. res, err = markdown.RenderRawString(markup.NewTestRenderContext(), mac)
  230. assert.NoError(t, err)
  231. 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)
  232. dos := strings.ReplaceAll(str, "\n", "\r\n")
  233. res, err = markdown.RenderRawString(markup.NewTestRenderContext(), dos)
  234. assert.NoError(t, err)
  235. 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)
  236. }
  237. test(t, "\nOne\nTwo\nThree", 1)
  238. test(t, "\n\nOne\nTwo\nThree", 1)
  239. test(t, "\n\nOne\nTwo\nThree\n\n\n", 1)
  240. test(t, "A\n\nB\nC\n", 2)
  241. test(t, "A\n\n\nB\nC\n", 2)
  242. }
  243. func TestMarkdownRenderRaw(t *testing.T) {
  244. testcases := [][]byte{
  245. { // clusterfuzz_testcase_minimized_fuzz_markdown_render_raw_6267570554535936
  246. 0x2a, 0x20, 0x2d, 0x0a, 0x09, 0x20, 0x60, 0x5b, 0x0a, 0x09, 0x20, 0x60,
  247. 0x5b,
  248. },
  249. { // clusterfuzz_testcase_minimized_fuzz_markdown_render_raw_6278827345051648
  250. 0x2d, 0x20, 0x2d, 0x0d, 0x09, 0x60, 0x0d, 0x09, 0x60,
  251. },
  252. { // clusterfuzz_testcase_minimized_fuzz_markdown_render_raw_6016973788020736[] = {
  253. 0x7b, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x3d, 0x35, 0x7d, 0x0a, 0x3d,
  254. },
  255. }
  256. for _, testcase := range testcases {
  257. log.Info("Test markdown render error with fuzzy data: %x, the following errors can be recovered", testcase)
  258. _, err := markdown.RenderRawString(markup.NewTestRenderContext(), string(testcase))
  259. assert.NoError(t, err)
  260. }
  261. }
  262. func TestRenderSiblingImages_Issue12925(t *testing.T) {
  263. testcase := `![image1](/image1)
  264. ![image2](/image2)
  265. `
  266. expected := `<p><a href="/image1" target="_blank" rel="nofollow noopener"><img src="/image1" alt="image1"/></a>
  267. <a href="/image2" target="_blank" rel="nofollow noopener"><img src="/image2" alt="image2"/></a></p>
  268. `
  269. res, err := markdown.RenderString(markup.NewTestRenderContext(), testcase)
  270. assert.NoError(t, err)
  271. assert.Equal(t, expected, string(res))
  272. }
  273. func TestRenderEmojiInLinks_Issue12331(t *testing.T) {
  274. testcase := `[Link with emoji :moon: in text](https://gitea.io)`
  275. 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>
  276. `
  277. res, err := markdown.RenderString(markup.NewTestRenderContext(), testcase)
  278. assert.NoError(t, err)
  279. assert.Equal(t, template.HTML(expected), res)
  280. }
  281. func TestColorPreview(t *testing.T) {
  282. const nl = "\n"
  283. positiveTests := []struct {
  284. testcase string
  285. expected string
  286. }{
  287. { // do not render color names
  288. "The CSS class `red` is there",
  289. "<p>The CSS class <code>red</code> is there</p>\n",
  290. },
  291. { // hex
  292. "`#FF0000`",
  293. `<p><code>#FF0000<span class="color-preview" style="background-color: #FF0000"></span></code></p>` + nl,
  294. },
  295. { // rgb
  296. "`rgb(16, 32, 64)`",
  297. `<p><code>rgb(16, 32, 64)<span class="color-preview" style="background-color: rgb(16, 32, 64)"></span></code></p>` + nl,
  298. },
  299. { // short hex
  300. "This is the color white `#0a0`",
  301. `<p>This is the color white <code>#0a0<span class="color-preview" style="background-color: #0a0"></span></code></p>` + nl,
  302. },
  303. { // hsl
  304. "HSL stands for hue, saturation, and lightness. An example: `hsl(0, 100%, 50%)`.",
  305. `<p>HSL stands for hue, saturation, and lightness. An example: <code>hsl(0, 100%, 50%)<span class="color-preview" style="background-color: hsl(0, 100%, 50%)"></span></code>.</p>` + nl,
  306. },
  307. { // uppercase hsl
  308. "HSL stands for hue, saturation, and lightness. An example: `HSL(0, 100%, 50%)`.",
  309. `<p>HSL stands for hue, saturation, and lightness. An example: <code>HSL(0, 100%, 50%)<span class="color-preview" style="background-color: HSL(0, 100%, 50%)"></span></code>.</p>` + nl,
  310. },
  311. }
  312. for _, test := range positiveTests {
  313. res, err := markdown.RenderString(markup.NewTestRenderContext(), test.testcase)
  314. assert.NoError(t, err, "Unexpected error in testcase: %q", test.testcase)
  315. assert.Equal(t, template.HTML(test.expected), res, "Unexpected result in testcase %q", test.testcase)
  316. }
  317. negativeTests := []string{
  318. // not a color code
  319. "`FF0000`",
  320. // inside a code block
  321. "```javascript" + nl + `const red = "#FF0000";` + nl + "```",
  322. // no backticks
  323. "rgb(166, 32, 64)",
  324. // typo
  325. "`hsI(0, 100%, 50%)`",
  326. // looks like a color but not really
  327. "`hsl(40, 60, 80)`",
  328. }
  329. for _, test := range negativeTests {
  330. res, err := markdown.RenderString(markup.NewTestRenderContext(), test)
  331. assert.NoError(t, err, "Unexpected error in testcase: %q", test)
  332. assert.NotContains(t, res, `<span class="color-preview" style="background-color: `, "Unexpected result in testcase %q", test)
  333. }
  334. }
  335. func TestMarkdownFrontmatter(t *testing.T) {
  336. testcases := []struct {
  337. name string
  338. input string
  339. expected string
  340. }{
  341. {
  342. "MapInFrontmatter",
  343. `---
  344. key1: val1
  345. key2: val2
  346. ---
  347. test
  348. `,
  349. `<details class="frontmatter-content"><summary><span>octicon-table(12/)</span> key1, key2</summary><table>
  350. <thead>
  351. <tr>
  352. <th>key1</th>
  353. <th>key2</th>
  354. </tr>
  355. </thead>
  356. <tbody>
  357. <tr>
  358. <td>val1</td>
  359. <td>val2</td>
  360. </tr>
  361. </tbody>
  362. </table>
  363. </details><p>test</p>
  364. `,
  365. },
  366. {
  367. "ListInFrontmatter",
  368. `---
  369. - item1
  370. - item2
  371. ---
  372. test
  373. `,
  374. `- item1
  375. - item2
  376. <p>test</p>
  377. `,
  378. },
  379. {
  380. "StringInFrontmatter",
  381. `---
  382. anything
  383. ---
  384. test
  385. `,
  386. `anything
  387. <p>test</p>
  388. `,
  389. },
  390. {
  391. // data-source-position should take into account YAML frontmatter.
  392. "ListAfterFrontmatter",
  393. `---
  394. foo: bar
  395. ---
  396. - [ ] task 1`,
  397. `<details class="frontmatter-content"><summary><span>octicon-table(12/)</span> foo</summary><table>
  398. <thead>
  399. <tr>
  400. <th>foo</th>
  401. </tr>
  402. </thead>
  403. <tbody>
  404. <tr>
  405. <td>bar</td>
  406. </tr>
  407. </tbody>
  408. </table>
  409. </details><ul>
  410. <li class="task-list-item"><input type="checkbox" disabled="" data-source-position="19"/>task 1</li>
  411. </ul>
  412. `,
  413. },
  414. }
  415. for _, test := range testcases {
  416. res, err := markdown.RenderString(markup.NewTestRenderContext(), test.input)
  417. assert.NoError(t, err, "Unexpected error in testcase: %q", test.name)
  418. assert.Equal(t, test.expected, string(res), "Unexpected result in testcase %q", test.name)
  419. }
  420. }
  421. func TestRenderLinks(t *testing.T) {
  422. input := ` space @mention-user${SPACE}${SPACE}
  423. /just/a/path.bin
  424. https://example.com/file.bin
  425. [local link](file.bin)
  426. [remote link](https://example.com)
  427. [[local link|file.bin]]
  428. [[remote link|https://example.com]]
  429. ![local image](image.jpg)
  430. ![local image](path/file)
  431. ![local image](/path/file)
  432. ![remote image](https://example.com/image.jpg)
  433. [[local image|image.jpg]]
  434. [[remote link|https://example.com/image.jpg]]
  435. https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash
  436. com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare
  437. https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb
  438. com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit
  439. :+1:
  440. mail@domain.com
  441. @mention-user test
  442. #123
  443. space${SPACE}${SPACE}
  444. `
  445. input = strings.ReplaceAll(input, "${SPACE}", " ") // replace ${SPACE} with " ", to avoid some editor's auto-trimming
  446. expected := `<p>space @mention-user<br/>
  447. /just/a/path.bin
  448. <a href="https://example.com/file.bin" rel="nofollow">https://example.com/file.bin</a>
  449. <a href="/file.bin" rel="nofollow">local link</a>
  450. <a href="https://example.com" rel="nofollow">remote link</a>
  451. <a href="/file.bin" rel="nofollow">local link</a>
  452. <a href="https://example.com" rel="nofollow">remote link</a>
  453. <a href="/image.jpg" target="_blank" rel="nofollow noopener"><img src="/image.jpg" alt="local image"/></a>
  454. <a href="/path/file" target="_blank" rel="nofollow noopener"><img src="/path/file" alt="local image"/></a>
  455. <a href="/path/file" target="_blank" rel="nofollow noopener"><img src="/path/file" alt="local image"/></a>
  456. <a href="https://example.com/image.jpg" target="_blank" rel="nofollow noopener"><img src="https://example.com/image.jpg" alt="remote image"/></a>
  457. <a href="/image.jpg" rel="nofollow"><img src="/image.jpg" title="local image" alt="local image"/></a>
  458. <a href="https://example.com/image.jpg" rel="nofollow"><img src="https://example.com/image.jpg" title="remote link" alt="remote link"/></a>
  459. <a href="https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash" rel="nofollow"><code>88fc37a3c0...12fc37a3c0 (hash)</code></a>
  460. com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare
  461. <a href="https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb" rel="nofollow"><code>88fc37a3c0</code></a>
  462. com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit
  463. <span class="emoji" aria-label="thumbs up">👍</span>
  464. <a href="mailto:mail@domain.com" rel="nofollow">mail@domain.com</a>
  465. @mention-user test
  466. #123
  467. space</p>
  468. `
  469. defer test.MockVariableValue(&markup.RenderBehaviorForTesting.DisableAdditionalAttributes, true)()
  470. result, err := markdown.RenderString(markup.NewTestRenderContext(localMetas), input)
  471. assert.NoError(t, err)
  472. assert.Equal(t, expected, string(result))
  473. }
  474. func TestMarkdownLink(t *testing.T) {
  475. defer test.MockVariableValue(&markup.RenderBehaviorForTesting.DisableAdditionalAttributes, true)()
  476. input := `<a href=foo>link1</a>
  477. <a href='/foo'>link2</a>
  478. <a href="#foo">link3</a>`
  479. result, err := markdown.RenderString(markup.NewTestRenderContext("/base", localMetas), input)
  480. assert.NoError(t, err)
  481. assert.Equal(t, `<p><a href="/base/foo" rel="nofollow">link1</a>
  482. <a href="/base/foo" rel="nofollow">link2</a>
  483. <a href="#user-content-foo" rel="nofollow">link3</a></p>
  484. `, string(result))
  485. }