Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.

markdown_test.go 49KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013
  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. "os"
  8. "strings"
  9. "testing"
  10. "code.gitea.io/gitea/models/unittest"
  11. "code.gitea.io/gitea/modules/git"
  12. "code.gitea.io/gitea/modules/log"
  13. "code.gitea.io/gitea/modules/markup"
  14. "code.gitea.io/gitea/modules/markup/markdown"
  15. "code.gitea.io/gitea/modules/setting"
  16. "code.gitea.io/gitea/modules/svg"
  17. "code.gitea.io/gitea/modules/util"
  18. "github.com/stretchr/testify/assert"
  19. "golang.org/x/text/cases"
  20. "golang.org/x/text/language"
  21. )
  22. const (
  23. AppURL = "http://localhost:3000/"
  24. FullURL = AppURL + "gogits/gogs/"
  25. )
  26. // these values should match the const above
  27. var localMetas = map[string]string{
  28. "user": "gogits",
  29. "repo": "gogs",
  30. "repoPath": "../../../tests/gitea-repositories-meta/user13/repo11.git/",
  31. }
  32. func TestMain(m *testing.M) {
  33. unittest.InitSettings()
  34. if err := git.InitSimple(context.Background()); err != nil {
  35. log.Fatal("git init failed, err: %v", err)
  36. }
  37. markup.Init(&markup.ProcessorHelper{
  38. IsUsernameMentionable: func(ctx context.Context, username string) bool {
  39. return username == "r-lyeh"
  40. },
  41. })
  42. os.Exit(m.Run())
  43. }
  44. func TestRender_StandardLinks(t *testing.T) {
  45. setting.AppURL = AppURL
  46. test := func(input, expected, expectedWiki string) {
  47. buffer, err := markdown.RenderString(&markup.RenderContext{
  48. Ctx: git.DefaultContext,
  49. Links: markup.Links{
  50. Base: FullURL,
  51. },
  52. }, input)
  53. assert.NoError(t, err)
  54. assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(string(buffer)))
  55. buffer, err = markdown.RenderString(&markup.RenderContext{
  56. Ctx: git.DefaultContext,
  57. Links: markup.Links{
  58. Base: FullURL,
  59. },
  60. IsWiki: true,
  61. }, input)
  62. assert.NoError(t, err)
  63. assert.Equal(t, strings.TrimSpace(expectedWiki), strings.TrimSpace(string(buffer)))
  64. }
  65. googleRendered := `<p><a href="https://google.com/" rel="nofollow">https://google.com/</a></p>`
  66. test("<https://google.com/>", googleRendered, googleRendered)
  67. lnk := util.URLJoin(FullURL, "WikiPage")
  68. lnkWiki := util.URLJoin(FullURL, "wiki", "WikiPage")
  69. test("[WikiPage](WikiPage)",
  70. `<p><a href="`+lnk+`" rel="nofollow">WikiPage</a></p>`,
  71. `<p><a href="`+lnkWiki+`" rel="nofollow">WikiPage</a></p>`)
  72. }
  73. func TestRender_Images(t *testing.T) {
  74. setting.AppURL = AppURL
  75. test := func(input, expected string) {
  76. buffer, err := markdown.RenderString(&markup.RenderContext{
  77. Ctx: git.DefaultContext,
  78. Links: markup.Links{
  79. Base: FullURL,
  80. },
  81. }, input)
  82. assert.NoError(t, err)
  83. assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(string(buffer)))
  84. }
  85. url := "../../.images/src/02/train.jpg"
  86. title := "Train"
  87. href := "https://gitea.io"
  88. result := util.URLJoin(FullURL, url)
  89. // hint: With Markdown v2.5.2, there is a new syntax: [link](URL){:target="_blank"} , but we do not support it now
  90. test(
  91. "!["+title+"]("+url+")",
  92. `<p><a href="`+result+`" target="_blank" rel="nofollow noopener"><img src="`+result+`" alt="`+title+`"/></a></p>`)
  93. test(
  94. "[["+title+"|"+url+"]]",
  95. `<p><a href="`+result+`" rel="nofollow"><img src="`+result+`" title="`+title+`" alt="`+title+`"/></a></p>`)
  96. test(
  97. "[!["+title+"]("+url+")]("+href+")",
  98. `<p><a href="`+href+`" rel="nofollow"><img src="`+result+`" alt="`+title+`"/></a></p>`)
  99. test(
  100. "!["+title+"]("+url+")",
  101. `<p><a href="`+result+`" target="_blank" rel="nofollow noopener"><img src="`+result+`" alt="`+title+`"/></a></p>`)
  102. test(
  103. "[["+title+"|"+url+"]]",
  104. `<p><a href="`+result+`" rel="nofollow"><img src="`+result+`" title="`+title+`" alt="`+title+`"/></a></p>`)
  105. test(
  106. "[!["+title+"]("+url+")]("+href+")",
  107. `<p><a href="`+href+`" rel="nofollow"><img src="`+result+`" alt="`+title+`"/></a></p>`)
  108. }
  109. func testAnswers(baseURLContent, baseURLImages string) []string {
  110. return []string{
  111. `<p>Wiki! Enjoy :)</p>
  112. <ul>
  113. <li><a href="` + baseURLContent + `/Links" rel="nofollow">Links, Language bindings, Engine bindings</a></li>
  114. <li><a href="` + baseURLContent + `/Tips" rel="nofollow">Tips</a></li>
  115. </ul>
  116. <p>See commit <a href="/gogits/gogs/commit/65f1bf27bc" rel="nofollow"><code>65f1bf27bc</code></a></p>
  117. <p>Ideas and codes</p>
  118. <ul>
  119. <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>
  120. <li>Bezier widget (by <a href="/r-lyeh" rel="nofollow">@r-lyeh</a>) <a href="http://localhost:3000/gogits/gogs/issues/786" class="ref-issue" rel="nofollow">#786</a></li>
  121. <li>Node graph editors <a href="https://github.com/ocornut/imgui/issues/306" rel="nofollow">https://github.com/ocornut/imgui/issues/306</a></li>
  122. <li><a href="` + baseURLContent + `/memory_editor_example" rel="nofollow">Memory Editor</a></li>
  123. <li><a href="` + baseURLContent + `/plot_var_example" rel="nofollow">Plot var helper</a></li>
  124. </ul>
  125. `,
  126. `<h2 id="user-content-what-is-wine-staging">What is Wine Staging?</h2>
  127. <p><strong>Wine Staging</strong> on website <a href="http://wine-staging.com" rel="nofollow">wine-staging.com</a>.</p>
  128. <h2 id="user-content-quick-links">Quick Links</h2>
  129. <p>Here are some links to the most important topics. You can find the full list of pages at the sidebar.</p>
  130. <table>
  131. <thead>
  132. <tr>
  133. <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>
  134. <th><a href="` + baseURLContent + `/Installation" rel="nofollow">Installation</a></th>
  135. </tr>
  136. </thead>
  137. <tbody>
  138. <tr>
  139. <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>
  140. <td><a href="` + baseURLContent + `/Usage" rel="nofollow">Usage</a></td>
  141. </tr>
  142. </tbody>
  143. </table>
  144. `,
  145. `<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>
  146. <ol>
  147. <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/>
  148. <a href="` + baseURLImages + `/images/1.png" rel="nofollow"><img src="` + baseURLImages + `/images/1.png" title="1.png" alt="images/1.png"/></a></li>
  149. <li>Perform a test run by hitting the Run! button.<br/>
  150. <a href="` + baseURLImages + `/images/2.png" rel="nofollow"><img src="` + baseURLImages + `/images/2.png" title="2.png" alt="images/2.png"/></a></li>
  151. </ol>
  152. <h2 id="user-content-custom-id">More tests</h2>
  153. <p>(from <a href="https://www.markdownguide.org/extended-syntax/" rel="nofollow">https://www.markdownguide.org/extended-syntax/</a>)</p>
  154. <h3 id="user-content-checkboxes">Checkboxes</h3>
  155. <ul>
  156. <li class="task-list-item"><input type="checkbox" disabled="" data-source-position="434"/>unchecked</li>
  157. <li class="task-list-item"><input type="checkbox" disabled="" data-source-position="450" checked=""/>checked</li>
  158. <li class="task-list-item"><input type="checkbox" disabled="" data-source-position="464"/>still unchecked</li>
  159. </ul>
  160. <h3 id="user-content-definition-list">Definition list</h3>
  161. <dl>
  162. <dt>First Term</dt>
  163. <dd>This is the definition of the first term.</dd>
  164. <dt>Second Term</dt>
  165. <dd>This is one definition of the second term.</dd>
  166. <dd>This is another definition of the second term.</dd>
  167. </dl>
  168. <h3 id="user-content-footnotes">Footnotes</h3>
  169. <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>
  170. <div>
  171. <hr/>
  172. <ol>
  173. <li id="fn:user-content-1">
  174. <p>This is the first footnote. <a href="#fnref:user-content-1" rel="nofollow">↩︎</a></p>
  175. </li>
  176. <li id="fn:user-content-bignote">
  177. <p>Here is one with multiple paragraphs and code.</p>
  178. <p>Indent paragraphs to include them in the footnote.</p>
  179. <p><code>{ my code }</code></p>
  180. <p>Add as many paragraphs as you like. <a href="#fnref:user-content-bignote" rel="nofollow">↩︎</a></p>
  181. </li>
  182. </ol>
  183. </div>
  184. `, `<ul>
  185. <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>
  186. </ul>
  187. <hr/>
  188. <p>This PR has been generated by <a href="https://github.com/renovatebot/renovate" rel="nofollow">Renovate Bot</a>.</p>
  189. `,
  190. }
  191. }
  192. // Test cases without ambiguous links
  193. var sameCases = []string{
  194. // dear imgui wiki markdown extract: special wiki syntax
  195. `Wiki! Enjoy :)
  196. - [[Links, Language bindings, Engine bindings|Links]]
  197. - [[Tips]]
  198. See commit 65f1bf27bc
  199. Ideas and codes
  200. - Bezier widget (by @r-lyeh) ` + AppURL + `ocornut/imgui/issues/786
  201. - Bezier widget (by @r-lyeh) ` + AppURL + `gogits/gogs/issues/786
  202. - Node graph editors https://github.com/ocornut/imgui/issues/306
  203. - [[Memory Editor|memory_editor_example]]
  204. - [[Plot var helper|plot_var_example]]`,
  205. // wine-staging wiki home extract: tables, special wiki syntax, images
  206. `## What is Wine Staging?
  207. **Wine Staging** on website [wine-staging.com](http://wine-staging.com).
  208. ## Quick Links
  209. Here are some links to the most important topics. You can find the full list of pages at the sidebar.
  210. | [[images/icon-install.png]] | [[Installation]] |
  211. |--------------------------------|----------------------------------------------------------|
  212. | [[images/icon-usage.png]] | [[Usage]] |
  213. `,
  214. // libgdx wiki page: inline images with special syntax
  215. `[Excelsior JET](http://www.excelsiorjet.com/) allows you to create native executables for Windows, Linux and Mac OS X.
  216. 1. [Package your libGDX application](https://github.com/libgdx/libgdx/wiki/Gradle-on-the-Commandline#packaging-for-the-desktop)
  217. [[images/1.png]]
  218. 2. Perform a test run by hitting the Run! button.
  219. [[images/2.png]]
  220. ## More tests {#custom-id}
  221. (from https://www.markdownguide.org/extended-syntax/)
  222. ### Checkboxes
  223. - [ ] unchecked
  224. - [x] checked
  225. - [ ] still unchecked
  226. ### Definition list
  227. First Term
  228. : This is the definition of the first term.
  229. Second Term
  230. : This is one definition of the second term.
  231. : This is another definition of the second term.
  232. ### Footnotes
  233. Here is a simple footnote,[^1] and here is a longer one.[^bignote]
  234. [^1]: This is the first footnote.
  235. [^bignote]: Here is one with multiple paragraphs and code.
  236. Indent paragraphs to include them in the footnote.
  237. ` + "`{ my code }`" + `
  238. Add as many paragraphs as you like.
  239. `,
  240. `
  241. - [ ] <!-- rebase-check --> If you want to rebase/retry this PR, click this checkbox.
  242. ---
  243. This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
  244. <!-- test-comment -->`,
  245. }
  246. func TestTotal_RenderWiki(t *testing.T) {
  247. setting.AppURL = AppURL
  248. answers := testAnswers(util.URLJoin(FullURL, "wiki"), util.URLJoin(FullURL, "wiki", "raw"))
  249. for i := 0; i < len(sameCases); i++ {
  250. line, err := markdown.RenderString(&markup.RenderContext{
  251. Ctx: git.DefaultContext,
  252. Links: markup.Links{
  253. Base: FullURL,
  254. },
  255. Metas: localMetas,
  256. IsWiki: true,
  257. }, sameCases[i])
  258. assert.NoError(t, err)
  259. assert.Equal(t, template.HTML(answers[i]), line)
  260. }
  261. testCases := []string{
  262. // Guard wiki sidebar: special syntax
  263. `[[Guardfile-DSL / Configuring-Guard|Guardfile-DSL---Configuring-Guard]]`,
  264. // rendered
  265. `<p><a href="` + FullURL + `wiki/Guardfile-DSL---Configuring-Guard" rel="nofollow">Guardfile-DSL / Configuring-Guard</a></p>
  266. `,
  267. // special syntax
  268. `[[Name|Link]]`,
  269. // rendered
  270. `<p><a href="` + FullURL + `wiki/Link" rel="nofollow">Name</a></p>
  271. `,
  272. }
  273. for i := 0; i < len(testCases); i += 2 {
  274. line, err := markdown.RenderString(&markup.RenderContext{
  275. Ctx: git.DefaultContext,
  276. Links: markup.Links{
  277. Base: FullURL,
  278. },
  279. IsWiki: true,
  280. }, testCases[i])
  281. assert.NoError(t, err)
  282. assert.Equal(t, template.HTML(testCases[i+1]), line)
  283. }
  284. }
  285. func TestTotal_RenderString(t *testing.T) {
  286. setting.AppURL = AppURL
  287. answers := testAnswers(util.URLJoin(FullURL, "src", "master"), util.URLJoin(FullURL, "media", "master"))
  288. for i := 0; i < len(sameCases); i++ {
  289. line, err := markdown.RenderString(&markup.RenderContext{
  290. Ctx: git.DefaultContext,
  291. Links: markup.Links{
  292. Base: FullURL,
  293. BranchPath: "master",
  294. },
  295. Metas: localMetas,
  296. }, sameCases[i])
  297. assert.NoError(t, err)
  298. assert.Equal(t, template.HTML(answers[i]), line)
  299. }
  300. testCases := []string{}
  301. for i := 0; i < len(testCases); i += 2 {
  302. line, err := markdown.RenderString(&markup.RenderContext{
  303. Ctx: git.DefaultContext,
  304. Links: markup.Links{
  305. Base: FullURL,
  306. },
  307. }, testCases[i])
  308. assert.NoError(t, err)
  309. assert.Equal(t, template.HTML(testCases[i+1]), line)
  310. }
  311. }
  312. func TestRender_RenderParagraphs(t *testing.T) {
  313. test := func(t *testing.T, str string, cnt int) {
  314. res, err := markdown.RenderRawString(&markup.RenderContext{Ctx: git.DefaultContext}, str)
  315. assert.NoError(t, err)
  316. 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)
  317. mac := strings.ReplaceAll(str, "\n", "\r")
  318. res, err = markdown.RenderRawString(&markup.RenderContext{Ctx: git.DefaultContext}, mac)
  319. assert.NoError(t, err)
  320. 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)
  321. dos := strings.ReplaceAll(str, "\n", "\r\n")
  322. res, err = markdown.RenderRawString(&markup.RenderContext{Ctx: git.DefaultContext}, dos)
  323. assert.NoError(t, err)
  324. 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)
  325. }
  326. test(t, "\nOne\nTwo\nThree", 1)
  327. test(t, "\n\nOne\nTwo\nThree", 1)
  328. test(t, "\n\nOne\nTwo\nThree\n\n\n", 1)
  329. test(t, "A\n\nB\nC\n", 2)
  330. test(t, "A\n\n\nB\nC\n", 2)
  331. }
  332. func TestMarkdownRenderRaw(t *testing.T) {
  333. testcases := [][]byte{
  334. { // clusterfuzz_testcase_minimized_fuzz_markdown_render_raw_6267570554535936
  335. 0x2a, 0x20, 0x2d, 0x0a, 0x09, 0x20, 0x60, 0x5b, 0x0a, 0x09, 0x20, 0x60,
  336. 0x5b,
  337. },
  338. { // clusterfuzz_testcase_minimized_fuzz_markdown_render_raw_6278827345051648
  339. 0x2d, 0x20, 0x2d, 0x0d, 0x09, 0x60, 0x0d, 0x09, 0x60,
  340. },
  341. { // clusterfuzz_testcase_minimized_fuzz_markdown_render_raw_6016973788020736[] = {
  342. 0x7b, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x3d, 0x35, 0x7d, 0x0a, 0x3d,
  343. },
  344. }
  345. for _, testcase := range testcases {
  346. log.Info("Test markdown render error with fuzzy data: %x, the following errors can be recovered", testcase)
  347. _, err := markdown.RenderRawString(&markup.RenderContext{Ctx: git.DefaultContext}, string(testcase))
  348. assert.NoError(t, err)
  349. }
  350. }
  351. func TestRenderSiblingImages_Issue12925(t *testing.T) {
  352. testcase := `![image1](/image1)
  353. ![image2](/image2)
  354. `
  355. expected := `<p><a href="/image1" target="_blank" rel="nofollow noopener"><img src="/image1" alt="image1"></a><br>
  356. <a href="/image2" target="_blank" rel="nofollow noopener"><img src="/image2" alt="image2"></a></p>
  357. `
  358. res, err := markdown.RenderRawString(&markup.RenderContext{Ctx: git.DefaultContext}, testcase)
  359. assert.NoError(t, err)
  360. assert.Equal(t, expected, res)
  361. }
  362. func TestRenderEmojiInLinks_Issue12331(t *testing.T) {
  363. testcase := `[Link with emoji :moon: in text](https://gitea.io)`
  364. 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>
  365. `
  366. res, err := markdown.RenderString(&markup.RenderContext{Ctx: git.DefaultContext}, testcase)
  367. assert.NoError(t, err)
  368. assert.Equal(t, template.HTML(expected), res)
  369. }
  370. func TestColorPreview(t *testing.T) {
  371. const nl = "\n"
  372. positiveTests := []struct {
  373. testcase string
  374. expected string
  375. }{
  376. { // do not render color names
  377. "The CSS class `red` is there",
  378. "<p>The CSS class <code>red</code> is there</p>\n",
  379. },
  380. { // hex
  381. "`#FF0000`",
  382. `<p><code>#FF0000<span class="color-preview" style="background-color: #FF0000"></span></code></p>` + nl,
  383. },
  384. { // rgb
  385. "`rgb(16, 32, 64)`",
  386. `<p><code>rgb(16, 32, 64)<span class="color-preview" style="background-color: rgb(16, 32, 64)"></span></code></p>` + nl,
  387. },
  388. { // short hex
  389. "This is the color white `#0a0`",
  390. `<p>This is the color white <code>#0a0<span class="color-preview" style="background-color: #0a0"></span></code></p>` + nl,
  391. },
  392. { // hsl
  393. "HSL stands for hue, saturation, and lightness. An example: `hsl(0, 100%, 50%)`.",
  394. `<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,
  395. },
  396. { // uppercase hsl
  397. "HSL stands for hue, saturation, and lightness. An example: `HSL(0, 100%, 50%)`.",
  398. `<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,
  399. },
  400. }
  401. for _, test := range positiveTests {
  402. res, err := markdown.RenderString(&markup.RenderContext{Ctx: git.DefaultContext}, test.testcase)
  403. assert.NoError(t, err, "Unexpected error in testcase: %q", test.testcase)
  404. assert.Equal(t, template.HTML(test.expected), res, "Unexpected result in testcase %q", test.testcase)
  405. }
  406. negativeTests := []string{
  407. // not a color code
  408. "`FF0000`",
  409. // inside a code block
  410. "```javascript" + nl + `const red = "#FF0000";` + nl + "```",
  411. // no backticks
  412. "rgb(166, 32, 64)",
  413. // typo
  414. "`hsI(0, 100%, 50%)`",
  415. // looks like a color but not really
  416. "`hsl(40, 60, 80)`",
  417. }
  418. for _, test := range negativeTests {
  419. res, err := markdown.RenderString(&markup.RenderContext{Ctx: git.DefaultContext}, test)
  420. assert.NoError(t, err, "Unexpected error in testcase: %q", test)
  421. assert.NotContains(t, res, `<span class="color-preview" style="background-color: `, "Unexpected result in testcase %q", test)
  422. }
  423. }
  424. func TestMathBlock(t *testing.T) {
  425. const nl = "\n"
  426. testcases := []struct {
  427. testcase string
  428. expected string
  429. }{
  430. {
  431. "$a$",
  432. `<p><code class="language-math is-loading">a</code></p>` + nl,
  433. },
  434. {
  435. "$ a $",
  436. `<p><code class="language-math is-loading">a</code></p>` + nl,
  437. },
  438. {
  439. "$a$ $b$",
  440. `<p><code class="language-math is-loading">a</code> <code class="language-math is-loading">b</code></p>` + nl,
  441. },
  442. {
  443. `\(a\) \(b\)`,
  444. `<p><code class="language-math is-loading">a</code> <code class="language-math is-loading">b</code></p>` + nl,
  445. },
  446. {
  447. `$a$.`,
  448. `<p><code class="language-math is-loading">a</code>.</p>` + nl,
  449. },
  450. {
  451. `.$a$`,
  452. `<p>.$a$</p>` + nl,
  453. },
  454. {
  455. `$a a$b b$`,
  456. `<p>$a a$b b$</p>` + nl,
  457. },
  458. {
  459. `a a$b b`,
  460. `<p>a a$b b</p>` + nl,
  461. },
  462. {
  463. `a$b $a a$b b$`,
  464. `<p>a$b $a a$b b$</p>` + nl,
  465. },
  466. {
  467. "a$x$",
  468. `<p>a$x$</p>` + nl,
  469. },
  470. {
  471. "$x$a",
  472. `<p>$x$a</p>` + nl,
  473. },
  474. {
  475. "$$a$$",
  476. `<pre class="code-block is-loading"><code class="chroma language-math display">a</code></pre>` + nl,
  477. },
  478. }
  479. for _, test := range testcases {
  480. res, err := markdown.RenderString(&markup.RenderContext{Ctx: git.DefaultContext}, test.testcase)
  481. assert.NoError(t, err, "Unexpected error in testcase: %q", test.testcase)
  482. assert.Equal(t, template.HTML(test.expected), res, "Unexpected result in testcase %q", test.testcase)
  483. }
  484. }
  485. func TestTaskList(t *testing.T) {
  486. testcases := []struct {
  487. testcase string
  488. expected string
  489. }{
  490. {
  491. // data-source-position should take into account YAML frontmatter.
  492. `---
  493. foo: bar
  494. ---
  495. - [ ] task 1`,
  496. `<details><summary><i class="icon table"></i></summary><table>
  497. <thead>
  498. <tr>
  499. <th>foo</th>
  500. </tr>
  501. </thead>
  502. <tbody>
  503. <tr>
  504. <td>bar</td>
  505. </tr>
  506. </tbody>
  507. </table>
  508. </details><ul>
  509. <li class="task-list-item"><input type="checkbox" disabled="" data-source-position="19"/>task 1</li>
  510. </ul>
  511. `,
  512. },
  513. }
  514. for _, test := range testcases {
  515. res, err := markdown.RenderString(&markup.RenderContext{Ctx: git.DefaultContext}, test.testcase)
  516. assert.NoError(t, err, "Unexpected error in testcase: %q", test.testcase)
  517. assert.Equal(t, template.HTML(test.expected), res, "Unexpected result in testcase %q", test.testcase)
  518. }
  519. }
  520. func TestRenderLinks(t *testing.T) {
  521. input := ` space @mention-user${SPACE}${SPACE}
  522. /just/a/path.bin
  523. https://example.com/file.bin
  524. [local link](file.bin)
  525. [remote link](https://example.com)
  526. [[local link|file.bin]]
  527. [[remote link|https://example.com]]
  528. ![local image](image.jpg)
  529. ![local image](path/file)
  530. ![local image](/path/file)
  531. ![remote image](https://example.com/image.jpg)
  532. [[local image|image.jpg]]
  533. [[remote link|https://example.com/image.jpg]]
  534. https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash
  535. com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare
  536. https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb
  537. com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit
  538. :+1:
  539. mail@domain.com
  540. @mention-user test
  541. #123
  542. space${SPACE}${SPACE}
  543. `
  544. input = strings.ReplaceAll(input, "${SPACE}", " ") // replace ${SPACE} with " ", to avoid some editor's auto-trimming
  545. cases := []struct {
  546. Links markup.Links
  547. IsWiki bool
  548. Expected string
  549. }{
  550. { // 0
  551. Links: markup.Links{},
  552. IsWiki: false,
  553. Expected: `<p>space @mention-user<br/>
  554. /just/a/path.bin<br/>
  555. <a href="https://example.com/file.bin" rel="nofollow">https://example.com/file.bin</a><br/>
  556. <a href="/file.bin" rel="nofollow">local link</a><br/>
  557. <a href="https://example.com" rel="nofollow">remote link</a><br/>
  558. <a href="/src/file.bin" rel="nofollow">local link</a><br/>
  559. <a href="https://example.com" rel="nofollow">remote link</a><br/>
  560. <a href="/image.jpg" target="_blank" rel="nofollow noopener"><img src="/image.jpg" alt="local image"/></a><br/>
  561. <a href="/path/file" target="_blank" rel="nofollow noopener"><img src="/path/file" alt="local image"/></a><br/>
  562. <a href="/path/file" target="_blank" rel="nofollow noopener"><img src="/path/file" alt="local image"/></a><br/>
  563. <a href="https://example.com/image.jpg" target="_blank" rel="nofollow noopener"><img src="https://example.com/image.jpg" alt="remote image"/></a><br/>
  564. <a href="/image.jpg" rel="nofollow"><img src="/image.jpg" title="local image" alt="local image"/></a><br/>
  565. <a href="https://example.com/image.jpg" rel="nofollow"><img src="https://example.com/image.jpg" title="remote link" alt="remote link"/></a><br/>
  566. <a href="https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash" rel="nofollow">https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash</a><br/>
  567. com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare<br/>
  568. <a href="https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb" rel="nofollow">https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb</a><br/>
  569. com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit<br/>
  570. <span class="emoji" aria-label="thumbs up">👍</span><br/>
  571. <a href="mailto:mail@domain.com" rel="nofollow">mail@domain.com</a><br/>
  572. @mention-user test<br/>
  573. #123<br/>
  574. space</p>
  575. `,
  576. },
  577. { // 1
  578. Links: markup.Links{},
  579. IsWiki: true,
  580. Expected: `<p>space @mention-user<br/>
  581. /just/a/path.bin<br/>
  582. <a href="https://example.com/file.bin" rel="nofollow">https://example.com/file.bin</a><br/>
  583. <a href="/wiki/raw/file.bin" rel="nofollow">local link</a><br/>
  584. <a href="https://example.com" rel="nofollow">remote link</a><br/>
  585. <a href="/wiki/raw/file.bin" rel="nofollow">local link</a><br/>
  586. <a href="https://example.com" rel="nofollow">remote link</a><br/>
  587. <a href="/wiki/raw/image.jpg" target="_blank" rel="nofollow noopener"><img src="/wiki/raw/image.jpg" alt="local image"/></a><br/>
  588. <a href="/wiki/raw/path/file" target="_blank" rel="nofollow noopener"><img src="/wiki/raw/path/file" alt="local image"/></a><br/>
  589. <a href="/wiki/raw/path/file" target="_blank" rel="nofollow noopener"><img src="/wiki/raw/path/file" alt="local image"/></a><br/>
  590. <a href="https://example.com/image.jpg" target="_blank" rel="nofollow noopener"><img src="https://example.com/image.jpg" alt="remote image"/></a><br/>
  591. <a href="/wiki/raw/image.jpg" rel="nofollow"><img src="/wiki/raw/image.jpg" title="local image" alt="local image"/></a><br/>
  592. <a href="https://example.com/image.jpg" rel="nofollow"><img src="https://example.com/image.jpg" title="remote link" alt="remote link"/></a><br/>
  593. <a href="https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash" rel="nofollow">https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash</a><br/>
  594. com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare<br/>
  595. <a href="https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb" rel="nofollow">https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb</a><br/>
  596. com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit<br/>
  597. <span class="emoji" aria-label="thumbs up">👍</span><br/>
  598. <a href="mailto:mail@domain.com" rel="nofollow">mail@domain.com</a><br/>
  599. @mention-user test<br/>
  600. #123<br/>
  601. space</p>
  602. `,
  603. },
  604. { // 2
  605. Links: markup.Links{
  606. Base: "https://gitea.io/",
  607. },
  608. IsWiki: false,
  609. Expected: `<p>space @mention-user<br/>
  610. /just/a/path.bin<br/>
  611. <a href="https://example.com/file.bin" rel="nofollow">https://example.com/file.bin</a><br/>
  612. <a href="https://gitea.io/file.bin" rel="nofollow">local link</a><br/>
  613. <a href="https://example.com" rel="nofollow">remote link</a><br/>
  614. <a href="https://gitea.io/src/file.bin" rel="nofollow">local link</a><br/>
  615. <a href="https://example.com" rel="nofollow">remote link</a><br/>
  616. <a href="https://gitea.io/image.jpg" target="_blank" rel="nofollow noopener"><img src="https://gitea.io/image.jpg" alt="local image"/></a><br/>
  617. <a href="https://gitea.io/path/file" target="_blank" rel="nofollow noopener"><img src="https://gitea.io/path/file" alt="local image"/></a><br/>
  618. <a href="https://gitea.io/path/file" target="_blank" rel="nofollow noopener"><img src="https://gitea.io/path/file" alt="local image"/></a><br/>
  619. <a href="https://example.com/image.jpg" target="_blank" rel="nofollow noopener"><img src="https://example.com/image.jpg" alt="remote image"/></a><br/>
  620. <a href="https://gitea.io/image.jpg" rel="nofollow"><img src="https://gitea.io/image.jpg" title="local image" alt="local image"/></a><br/>
  621. <a href="https://example.com/image.jpg" rel="nofollow"><img src="https://example.com/image.jpg" title="remote link" alt="remote link"/></a><br/>
  622. <a href="https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash" rel="nofollow">https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash</a><br/>
  623. com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare<br/>
  624. <a href="https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb" rel="nofollow">https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb</a><br/>
  625. com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit<br/>
  626. <span class="emoji" aria-label="thumbs up">👍</span><br/>
  627. <a href="mailto:mail@domain.com" rel="nofollow">mail@domain.com</a><br/>
  628. @mention-user test<br/>
  629. #123<br/>
  630. space</p>
  631. `,
  632. },
  633. { // 3
  634. Links: markup.Links{
  635. Base: "https://gitea.io/",
  636. },
  637. IsWiki: true,
  638. Expected: `<p>space @mention-user<br/>
  639. /just/a/path.bin<br/>
  640. <a href="https://example.com/file.bin" rel="nofollow">https://example.com/file.bin</a><br/>
  641. <a href="https://gitea.io/wiki/raw/file.bin" rel="nofollow">local link</a><br/>
  642. <a href="https://example.com" rel="nofollow">remote link</a><br/>
  643. <a href="https://gitea.io/wiki/raw/file.bin" rel="nofollow">local link</a><br/>
  644. <a href="https://example.com" rel="nofollow">remote link</a><br/>
  645. <a href="https://gitea.io/wiki/raw/image.jpg" target="_blank" rel="nofollow noopener"><img src="https://gitea.io/wiki/raw/image.jpg" alt="local image"/></a><br/>
  646. <a href="https://gitea.io/wiki/raw/path/file" target="_blank" rel="nofollow noopener"><img src="https://gitea.io/wiki/raw/path/file" alt="local image"/></a><br/>
  647. <a href="https://gitea.io/wiki/raw/path/file" target="_blank" rel="nofollow noopener"><img src="https://gitea.io/wiki/raw/path/file" alt="local image"/></a><br/>
  648. <a href="https://example.com/image.jpg" target="_blank" rel="nofollow noopener"><img src="https://example.com/image.jpg" alt="remote image"/></a><br/>
  649. <a href="https://gitea.io/wiki/raw/image.jpg" rel="nofollow"><img src="https://gitea.io/wiki/raw/image.jpg" title="local image" alt="local image"/></a><br/>
  650. <a href="https://example.com/image.jpg" rel="nofollow"><img src="https://example.com/image.jpg" title="remote link" alt="remote link"/></a><br/>
  651. <a href="https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash" rel="nofollow">https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash</a><br/>
  652. com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare<br/>
  653. <a href="https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb" rel="nofollow">https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb</a><br/>
  654. com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit<br/>
  655. <span class="emoji" aria-label="thumbs up">👍</span><br/>
  656. <a href="mailto:mail@domain.com" rel="nofollow">mail@domain.com</a><br/>
  657. @mention-user test<br/>
  658. #123<br/>
  659. space</p>
  660. `,
  661. },
  662. { // 4
  663. Links: markup.Links{
  664. Base: "/relative/path",
  665. },
  666. IsWiki: false,
  667. Expected: `<p>space @mention-user<br/>
  668. /just/a/path.bin<br/>
  669. <a href="https://example.com/file.bin" rel="nofollow">https://example.com/file.bin</a><br/>
  670. <a href="/relative/path/file.bin" rel="nofollow">local link</a><br/>
  671. <a href="https://example.com" rel="nofollow">remote link</a><br/>
  672. <a href="/relative/path/src/file.bin" rel="nofollow">local link</a><br/>
  673. <a href="https://example.com" rel="nofollow">remote link</a><br/>
  674. <a href="/relative/path/image.jpg" target="_blank" rel="nofollow noopener"><img src="/relative/path/image.jpg" alt="local image"/></a><br/>
  675. <a href="/relative/path/path/file" target="_blank" rel="nofollow noopener"><img src="/relative/path/path/file" alt="local image"/></a><br/>
  676. <a href="/relative/path/path/file" target="_blank" rel="nofollow noopener"><img src="/relative/path/path/file" alt="local image"/></a><br/>
  677. <a href="https://example.com/image.jpg" target="_blank" rel="nofollow noopener"><img src="https://example.com/image.jpg" alt="remote image"/></a><br/>
  678. <a href="/relative/path/image.jpg" rel="nofollow"><img src="/relative/path/image.jpg" title="local image" alt="local image"/></a><br/>
  679. <a href="https://example.com/image.jpg" rel="nofollow"><img src="https://example.com/image.jpg" title="remote link" alt="remote link"/></a><br/>
  680. <a href="https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash" rel="nofollow">https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash</a><br/>
  681. com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare<br/>
  682. <a href="https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb" rel="nofollow">https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb</a><br/>
  683. com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit<br/>
  684. <span class="emoji" aria-label="thumbs up">👍</span><br/>
  685. <a href="mailto:mail@domain.com" rel="nofollow">mail@domain.com</a><br/>
  686. @mention-user test<br/>
  687. #123<br/>
  688. space</p>
  689. `,
  690. },
  691. { // 5
  692. Links: markup.Links{
  693. Base: "/relative/path",
  694. },
  695. IsWiki: true,
  696. Expected: `<p>space @mention-user<br/>
  697. /just/a/path.bin<br/>
  698. <a href="https://example.com/file.bin" rel="nofollow">https://example.com/file.bin</a><br/>
  699. <a href="/relative/path/wiki/raw/file.bin" rel="nofollow">local link</a><br/>
  700. <a href="https://example.com" rel="nofollow">remote link</a><br/>
  701. <a href="/relative/path/wiki/raw/file.bin" rel="nofollow">local link</a><br/>
  702. <a href="https://example.com" rel="nofollow">remote link</a><br/>
  703. <a href="/relative/path/wiki/raw/image.jpg" target="_blank" rel="nofollow noopener"><img src="/relative/path/wiki/raw/image.jpg" alt="local image"/></a><br/>
  704. <a href="/relative/path/wiki/raw/path/file" target="_blank" rel="nofollow noopener"><img src="/relative/path/wiki/raw/path/file" alt="local image"/></a><br/>
  705. <a href="/relative/path/wiki/raw/path/file" target="_blank" rel="nofollow noopener"><img src="/relative/path/wiki/raw/path/file" alt="local image"/></a><br/>
  706. <a href="https://example.com/image.jpg" target="_blank" rel="nofollow noopener"><img src="https://example.com/image.jpg" alt="remote image"/></a><br/>
  707. <a href="/relative/path/wiki/raw/image.jpg" rel="nofollow"><img src="/relative/path/wiki/raw/image.jpg" title="local image" alt="local image"/></a><br/>
  708. <a href="https://example.com/image.jpg" rel="nofollow"><img src="https://example.com/image.jpg" title="remote link" alt="remote link"/></a><br/>
  709. <a href="https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash" rel="nofollow">https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash</a><br/>
  710. com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare<br/>
  711. <a href="https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb" rel="nofollow">https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb</a><br/>
  712. com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit<br/>
  713. <span class="emoji" aria-label="thumbs up">👍</span><br/>
  714. <a href="mailto:mail@domain.com" rel="nofollow">mail@domain.com</a><br/>
  715. @mention-user test<br/>
  716. #123<br/>
  717. space</p>
  718. `,
  719. },
  720. { // 6
  721. Links: markup.Links{
  722. Base: "/user/repo",
  723. BranchPath: "branch/main",
  724. },
  725. IsWiki: false,
  726. Expected: `<p>space @mention-user<br/>
  727. /just/a/path.bin<br/>
  728. <a href="https://example.com/file.bin" rel="nofollow">https://example.com/file.bin</a><br/>
  729. <a href="/user/repo/src/branch/main/file.bin" rel="nofollow">local link</a><br/>
  730. <a href="https://example.com" rel="nofollow">remote link</a><br/>
  731. <a href="/user/repo/src/branch/main/file.bin" rel="nofollow">local link</a><br/>
  732. <a href="https://example.com" rel="nofollow">remote link</a><br/>
  733. <a href="/user/repo/media/branch/main/image.jpg" target="_blank" rel="nofollow noopener"><img src="/user/repo/media/branch/main/image.jpg" alt="local image"/></a><br/>
  734. <a href="/user/repo/media/branch/main/path/file" target="_blank" rel="nofollow noopener"><img src="/user/repo/media/branch/main/path/file" alt="local image"/></a><br/>
  735. <a href="/user/repo/media/branch/main/path/file" target="_blank" rel="nofollow noopener"><img src="/user/repo/media/branch/main/path/file" alt="local image"/></a><br/>
  736. <a href="https://example.com/image.jpg" target="_blank" rel="nofollow noopener"><img src="https://example.com/image.jpg" alt="remote image"/></a><br/>
  737. <a href="/user/repo/media/branch/main/image.jpg" rel="nofollow"><img src="/user/repo/media/branch/main/image.jpg" title="local image" alt="local image"/></a><br/>
  738. <a href="https://example.com/image.jpg" rel="nofollow"><img src="https://example.com/image.jpg" title="remote link" alt="remote link"/></a><br/>
  739. <a href="https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash" rel="nofollow">https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash</a><br/>
  740. com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare<br/>
  741. <a href="https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb" rel="nofollow">https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb</a><br/>
  742. com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit<br/>
  743. <span class="emoji" aria-label="thumbs up">👍</span><br/>
  744. <a href="mailto:mail@domain.com" rel="nofollow">mail@domain.com</a><br/>
  745. @mention-user test<br/>
  746. #123<br/>
  747. space</p>
  748. `,
  749. },
  750. { // 7
  751. Links: markup.Links{
  752. Base: "/relative/path",
  753. BranchPath: "branch/main",
  754. },
  755. IsWiki: true,
  756. Expected: `<p>space @mention-user<br/>
  757. /just/a/path.bin<br/>
  758. <a href="https://example.com/file.bin" rel="nofollow">https://example.com/file.bin</a><br/>
  759. <a href="/relative/path/wiki/raw/file.bin" rel="nofollow">local link</a><br/>
  760. <a href="https://example.com" rel="nofollow">remote link</a><br/>
  761. <a href="/relative/path/wiki/raw/file.bin" rel="nofollow">local link</a><br/>
  762. <a href="https://example.com" rel="nofollow">remote link</a><br/>
  763. <a href="/relative/path/wiki/raw/image.jpg" target="_blank" rel="nofollow noopener"><img src="/relative/path/wiki/raw/image.jpg" alt="local image"/></a><br/>
  764. <a href="/relative/path/wiki/raw/path/file" target="_blank" rel="nofollow noopener"><img src="/relative/path/wiki/raw/path/file" alt="local image"/></a><br/>
  765. <a href="/relative/path/wiki/raw/path/file" target="_blank" rel="nofollow noopener"><img src="/relative/path/wiki/raw/path/file" alt="local image"/></a><br/>
  766. <a href="https://example.com/image.jpg" target="_blank" rel="nofollow noopener"><img src="https://example.com/image.jpg" alt="remote image"/></a><br/>
  767. <a href="/relative/path/wiki/raw/image.jpg" rel="nofollow"><img src="/relative/path/wiki/raw/image.jpg" title="local image" alt="local image"/></a><br/>
  768. <a href="https://example.com/image.jpg" rel="nofollow"><img src="https://example.com/image.jpg" title="remote link" alt="remote link"/></a><br/>
  769. <a href="https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash" rel="nofollow">https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash</a><br/>
  770. com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare<br/>
  771. <a href="https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb" rel="nofollow">https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb</a><br/>
  772. com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit<br/>
  773. <span class="emoji" aria-label="thumbs up">👍</span><br/>
  774. <a href="mailto:mail@domain.com" rel="nofollow">mail@domain.com</a><br/>
  775. @mention-user test<br/>
  776. #123<br/>
  777. space</p>
  778. `,
  779. },
  780. { // 8
  781. Links: markup.Links{
  782. Base: "/user/repo",
  783. TreePath: "sub/folder",
  784. },
  785. IsWiki: false,
  786. Expected: `<p>space @mention-user<br/>
  787. /just/a/path.bin<br/>
  788. <a href="https://example.com/file.bin" rel="nofollow">https://example.com/file.bin</a><br/>
  789. <a href="/user/repo/file.bin" rel="nofollow">local link</a><br/>
  790. <a href="https://example.com" rel="nofollow">remote link</a><br/>
  791. <a href="/user/repo/src/sub/folder/file.bin" rel="nofollow">local link</a><br/>
  792. <a href="https://example.com" rel="nofollow">remote link</a><br/>
  793. <a href="/user/repo/image.jpg" target="_blank" rel="nofollow noopener"><img src="/user/repo/image.jpg" alt="local image"/></a><br/>
  794. <a href="/user/repo/path/file" target="_blank" rel="nofollow noopener"><img src="/user/repo/path/file" alt="local image"/></a><br/>
  795. <a href="/user/repo/path/file" target="_blank" rel="nofollow noopener"><img src="/user/repo/path/file" alt="local image"/></a><br/>
  796. <a href="https://example.com/image.jpg" target="_blank" rel="nofollow noopener"><img src="https://example.com/image.jpg" alt="remote image"/></a><br/>
  797. <a href="/user/repo/image.jpg" rel="nofollow"><img src="/user/repo/image.jpg" title="local image" alt="local image"/></a><br/>
  798. <a href="https://example.com/image.jpg" rel="nofollow"><img src="https://example.com/image.jpg" title="remote link" alt="remote link"/></a><br/>
  799. <a href="https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash" rel="nofollow">https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash</a><br/>
  800. com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare<br/>
  801. <a href="https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb" rel="nofollow">https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb</a><br/>
  802. com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit<br/>
  803. <span class="emoji" aria-label="thumbs up">👍</span><br/>
  804. <a href="mailto:mail@domain.com" rel="nofollow">mail@domain.com</a><br/>
  805. @mention-user test<br/>
  806. #123<br/>
  807. space</p>
  808. `,
  809. },
  810. { // 9
  811. Links: markup.Links{
  812. Base: "/relative/path",
  813. TreePath: "sub/folder",
  814. },
  815. IsWiki: true,
  816. Expected: `<p>space @mention-user<br/>
  817. /just/a/path.bin<br/>
  818. <a href="https://example.com/file.bin" rel="nofollow">https://example.com/file.bin</a><br/>
  819. <a href="/relative/path/wiki/raw/file.bin" rel="nofollow">local link</a><br/>
  820. <a href="https://example.com" rel="nofollow">remote link</a><br/>
  821. <a href="/relative/path/wiki/raw/file.bin" rel="nofollow">local link</a><br/>
  822. <a href="https://example.com" rel="nofollow">remote link</a><br/>
  823. <a href="/relative/path/wiki/raw/image.jpg" target="_blank" rel="nofollow noopener"><img src="/relative/path/wiki/raw/image.jpg" alt="local image"/></a><br/>
  824. <a href="/relative/path/wiki/raw/path/file" target="_blank" rel="nofollow noopener"><img src="/relative/path/wiki/raw/path/file" alt="local image"/></a><br/>
  825. <a href="/relative/path/wiki/raw/path/file" target="_blank" rel="nofollow noopener"><img src="/relative/path/wiki/raw/path/file" alt="local image"/></a><br/>
  826. <a href="https://example.com/image.jpg" target="_blank" rel="nofollow noopener"><img src="https://example.com/image.jpg" alt="remote image"/></a><br/>
  827. <a href="/relative/path/wiki/raw/image.jpg" rel="nofollow"><img src="/relative/path/wiki/raw/image.jpg" title="local image" alt="local image"/></a><br/>
  828. <a href="https://example.com/image.jpg" rel="nofollow"><img src="https://example.com/image.jpg" title="remote link" alt="remote link"/></a><br/>
  829. <a href="https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash" rel="nofollow">https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash</a><br/>
  830. com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare<br/>
  831. <a href="https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb" rel="nofollow">https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb</a><br/>
  832. com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit<br/>
  833. <span class="emoji" aria-label="thumbs up">👍</span><br/>
  834. <a href="mailto:mail@domain.com" rel="nofollow">mail@domain.com</a><br/>
  835. @mention-user test<br/>
  836. #123<br/>
  837. space</p>
  838. `,
  839. },
  840. { // 10
  841. Links: markup.Links{
  842. Base: "/user/repo",
  843. BranchPath: "branch/main",
  844. TreePath: "sub/folder",
  845. },
  846. IsWiki: false,
  847. Expected: `<p>space @mention-user<br/>
  848. /just/a/path.bin<br/>
  849. <a href="https://example.com/file.bin" rel="nofollow">https://example.com/file.bin</a><br/>
  850. <a href="/user/repo/src/branch/main/sub/folder/file.bin" rel="nofollow">local link</a><br/>
  851. <a href="https://example.com" rel="nofollow">remote link</a><br/>
  852. <a href="/user/repo/src/branch/main/sub/folder/file.bin" rel="nofollow">local link</a><br/>
  853. <a href="https://example.com" rel="nofollow">remote link</a><br/>
  854. <a href="/user/repo/media/branch/main/sub/folder/image.jpg" target="_blank" rel="nofollow noopener"><img src="/user/repo/media/branch/main/sub/folder/image.jpg" alt="local image"/></a><br/>
  855. <a href="/user/repo/media/branch/main/sub/folder/path/file" target="_blank" rel="nofollow noopener"><img src="/user/repo/media/branch/main/sub/folder/path/file" alt="local image"/></a><br/>
  856. <a href="/user/repo/media/branch/main/sub/folder/path/file" target="_blank" rel="nofollow noopener"><img src="/user/repo/media/branch/main/sub/folder/path/file" alt="local image"/></a><br/>
  857. <a href="https://example.com/image.jpg" target="_blank" rel="nofollow noopener"><img src="https://example.com/image.jpg" alt="remote image"/></a><br/>
  858. <a href="/user/repo/media/branch/main/sub/folder/image.jpg" rel="nofollow"><img src="/user/repo/media/branch/main/sub/folder/image.jpg" title="local image" alt="local image"/></a><br/>
  859. <a href="https://example.com/image.jpg" rel="nofollow"><img src="https://example.com/image.jpg" title="remote link" alt="remote link"/></a><br/>
  860. <a href="https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash" rel="nofollow">https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash</a><br/>
  861. com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare<br/>
  862. <a href="https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb" rel="nofollow">https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb</a><br/>
  863. com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit<br/>
  864. <span class="emoji" aria-label="thumbs up">👍</span><br/>
  865. <a href="mailto:mail@domain.com" rel="nofollow">mail@domain.com</a><br/>
  866. @mention-user test<br/>
  867. #123<br/>
  868. space</p>
  869. `,
  870. },
  871. { // 11
  872. Links: markup.Links{
  873. Base: "/relative/path",
  874. BranchPath: "branch/main",
  875. TreePath: "sub/folder",
  876. },
  877. IsWiki: true,
  878. Expected: `<p>space @mention-user<br/>
  879. /just/a/path.bin<br/>
  880. <a href="https://example.com/file.bin" rel="nofollow">https://example.com/file.bin</a><br/>
  881. <a href="/relative/path/wiki/raw/file.bin" rel="nofollow">local link</a><br/>
  882. <a href="https://example.com" rel="nofollow">remote link</a><br/>
  883. <a href="/relative/path/wiki/raw/file.bin" rel="nofollow">local link</a><br/>
  884. <a href="https://example.com" rel="nofollow">remote link</a><br/>
  885. <a href="/relative/path/wiki/raw/image.jpg" target="_blank" rel="nofollow noopener"><img src="/relative/path/wiki/raw/image.jpg" alt="local image"/></a><br/>
  886. <a href="/relative/path/wiki/raw/path/file" target="_blank" rel="nofollow noopener"><img src="/relative/path/wiki/raw/path/file" alt="local image"/></a><br/>
  887. <a href="/relative/path/wiki/raw/path/file" target="_blank" rel="nofollow noopener"><img src="/relative/path/wiki/raw/path/file" alt="local image"/></a><br/>
  888. <a href="https://example.com/image.jpg" target="_blank" rel="nofollow noopener"><img src="https://example.com/image.jpg" alt="remote image"/></a><br/>
  889. <a href="/relative/path/wiki/raw/image.jpg" rel="nofollow"><img src="/relative/path/wiki/raw/image.jpg" title="local image" alt="local image"/></a><br/>
  890. <a href="https://example.com/image.jpg" rel="nofollow"><img src="https://example.com/image.jpg" title="remote link" alt="remote link"/></a><br/>
  891. <a href="https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash" rel="nofollow">https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash</a><br/>
  892. com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare<br/>
  893. <a href="https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb" rel="nofollow">https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb</a><br/>
  894. com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit<br/>
  895. <span class="emoji" aria-label="thumbs up">👍</span><br/>
  896. <a href="mailto:mail@domain.com" rel="nofollow">mail@domain.com</a><br/>
  897. @mention-user test<br/>
  898. #123<br/>
  899. space</p>
  900. `,
  901. },
  902. }
  903. for i, c := range cases {
  904. result, err := markdown.RenderString(&markup.RenderContext{Ctx: context.Background(), Links: c.Links, IsWiki: c.IsWiki}, input)
  905. assert.NoError(t, err, "Unexpected error in testcase: %v", i)
  906. assert.Equal(t, template.HTML(c.Expected), result, "Unexpected result in testcase %v", i)
  907. }
  908. }
  909. func TestAttention(t *testing.T) {
  910. defer svg.MockIcon("octicon-info")()
  911. defer svg.MockIcon("octicon-light-bulb")()
  912. defer svg.MockIcon("octicon-report")()
  913. defer svg.MockIcon("octicon-alert")()
  914. defer svg.MockIcon("octicon-stop")()
  915. renderAttention := func(attention, icon string) string {
  916. tmpl := `<blockquote class="attention-header attention-{attention}"><p><svg class="attention-icon attention-{attention} svg {icon}" width="16" height="16"></svg><strong class="attention-{attention}">{Attention}</strong></p>`
  917. tmpl = strings.ReplaceAll(tmpl, "{attention}", attention)
  918. tmpl = strings.ReplaceAll(tmpl, "{icon}", icon)
  919. tmpl = strings.ReplaceAll(tmpl, "{Attention}", cases.Title(language.English).String(attention))
  920. return tmpl
  921. }
  922. test := func(input, expected string) {
  923. result, err := markdown.RenderString(&markup.RenderContext{Ctx: context.Background()}, input)
  924. assert.NoError(t, err)
  925. assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(string(result)))
  926. }
  927. test(`
  928. > [!NOTE]
  929. > text
  930. `, renderAttention("note", "octicon-info")+"\n<p>text</p>\n</blockquote>")
  931. test(`> [!note]`, renderAttention("note", "octicon-info")+"\n</blockquote>")
  932. test(`> [!tip]`, renderAttention("tip", "octicon-light-bulb")+"\n</blockquote>")
  933. test(`> [!important]`, renderAttention("important", "octicon-report")+"\n</blockquote>")
  934. test(`> [!warning]`, renderAttention("warning", "octicon-alert")+"\n</blockquote>")
  935. test(`> [!caution]`, renderAttention("caution", "octicon-stop")+"\n</blockquote>")
  936. }