Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.

markdown_test.go 49KB

Refactor path & config system (#25330) # The problem There were many "path tricks": * By default, Gitea uses its program directory as its work path * Gitea tries to use the "work path" to guess its "custom path" and "custom conf (app.ini)" * Users might want to use other directories as work path * The non-default work path should be passed to Gitea by GITEA_WORK_DIR or "--work-path" * But some Gitea processes are started without these values * The "serv" process started by OpenSSH server * The CLI sub-commands started by site admin * The paths are guessed by SetCustomPathAndConf again and again * The default values of "work path / custom path / custom conf" can be changed when compiling # The solution * Use `InitWorkPathAndCommonConfig` to handle these path tricks, and use test code to cover its behaviors. * When Gitea's web server runs, write the WORK_PATH to "app.ini", this value must be the most correct one, because if this value is not right, users would find that the web UI doesn't work and then they should be able to fix it. * Then all other sub-commands can use the WORK_PATH in app.ini to initialize their paths. * By the way, when Gitea starts for git protocol, it shouldn't output any log, otherwise the git protocol gets broken and client blocks forever. The "work path" priority is: WORK_PATH in app.ini > cmd arg --work-path > env var GITEA_WORK_DIR > builtin default The "app.ini" searching order is: cmd arg --config > cmd arg "work path / custom path" > env var "work path / custom path" > builtin default ## ⚠️ BREAKING If your instance's "work path / custom path / custom conf" doesn't meet the requirements (eg: work path must be absolute), Gitea will report a fatal error and exit. You need to set these values according to the error log. ---- Close #24818 Close #24222 Close #21606 Close #21498 Close #25107 Close #24981 Maybe close #24503 Replace #23301 Replace #22754 And maybe more
11 månader sedan
Check commit message hashes before making links (#7713) * Check commit message hashes before making links Previously, when formatting commit messages, anything that looked like SHA1 hashes was turned into a link using regex. This meant that certain phrases or numbers such as `777777` or `deadbeef` could be recognized as a commit even if the repository has no commit with those hashes. This change will make it so that anything that looks like a SHA1 hash using regex will then also be checked to ensure that there is a commit in the repository with that hash before making a link. Signed-off-by: Gary Kim <gary@garykim.dev> * Use gogit to check if commit exists This commit modifies the commit hash check in the render for commit messages to use gogit for better performance. Signed-off-by: Gary Kim <gary@garykim.dev> * Make code cleaner Signed-off-by: Gary Kim <gary@garykim.dev> * Use rev-parse to check if commit exists Signed-off-by: Gary Kim <gary@garykim.dev> * Add and modify tests for checking hashes in html link rendering Signed-off-by: Gary Kim <gary@garykim.dev> * Return error in sha1CurrentPatternProcessor Co-Authored-By: mrsdizzie <info@mrsdizzie.com> * Import Gitea log module Signed-off-by: Gary Kim <gary@garykim.dev> * Revert "Return error in sha1CurrentPatternProcessor" This reverts commit 28f561cac46ef7e51aa26aefcbe9aca4671366a6. Signed-off-by: Gary Kim <gary@garykim.dev> * Add debug logging to sha1CurrentPatternProcessor This will log errors by the git command run in sha1CurrentPatternProcessor if the error is one that was unexpected. Signed-off-by: Gary Kim <gary@garykim.dev>
4 år sedan
Refactor path & config system (#25330) # The problem There were many "path tricks": * By default, Gitea uses its program directory as its work path * Gitea tries to use the "work path" to guess its "custom path" and "custom conf (app.ini)" * Users might want to use other directories as work path * The non-default work path should be passed to Gitea by GITEA_WORK_DIR or "--work-path" * But some Gitea processes are started without these values * The "serv" process started by OpenSSH server * The CLI sub-commands started by site admin * The paths are guessed by SetCustomPathAndConf again and again * The default values of "work path / custom path / custom conf" can be changed when compiling # The solution * Use `InitWorkPathAndCommonConfig` to handle these path tricks, and use test code to cover its behaviors. * When Gitea's web server runs, write the WORK_PATH to "app.ini", this value must be the most correct one, because if this value is not right, users would find that the web UI doesn't work and then they should be able to fix it. * Then all other sub-commands can use the WORK_PATH in app.ini to initialize their paths. * By the way, when Gitea starts for git protocol, it shouldn't output any log, otherwise the git protocol gets broken and client blocks forever. The "work path" priority is: WORK_PATH in app.ini > cmd arg --work-path > env var GITEA_WORK_DIR > builtin default The "app.ini" searching order is: cmd arg --config > cmd arg "work path / custom path" > env var "work path / custom path" > builtin default ## ⚠️ BREAKING If your instance's "work path / custom path / custom conf" doesn't meet the requirements (eg: work path must be absolute), Gitea will report a fatal error and exit. You need to set these values according to the error log. ---- Close #24818 Close #24222 Close #21606 Close #21498 Close #25107 Close #24981 Maybe close #24503 Replace #23301 Replace #22754 And maybe more
11 månader sedan
Check commit message hashes before making links (#7713) * Check commit message hashes before making links Previously, when formatting commit messages, anything that looked like SHA1 hashes was turned into a link using regex. This meant that certain phrases or numbers such as `777777` or `deadbeef` could be recognized as a commit even if the repository has no commit with those hashes. This change will make it so that anything that looks like a SHA1 hash using regex will then also be checked to ensure that there is a commit in the repository with that hash before making a link. Signed-off-by: Gary Kim <gary@garykim.dev> * Use gogit to check if commit exists This commit modifies the commit hash check in the render for commit messages to use gogit for better performance. Signed-off-by: Gary Kim <gary@garykim.dev> * Make code cleaner Signed-off-by: Gary Kim <gary@garykim.dev> * Use rev-parse to check if commit exists Signed-off-by: Gary Kim <gary@garykim.dev> * Add and modify tests for checking hashes in html link rendering Signed-off-by: Gary Kim <gary@garykim.dev> * Return error in sha1CurrentPatternProcessor Co-Authored-By: mrsdizzie <info@mrsdizzie.com> * Import Gitea log module Signed-off-by: Gary Kim <gary@garykim.dev> * Revert "Return error in sha1CurrentPatternProcessor" This reverts commit 28f561cac46ef7e51aa26aefcbe9aca4671366a6. Signed-off-by: Gary Kim <gary@garykim.dev> * Add debug logging to sha1CurrentPatternProcessor This will log errors by the git command run in sha1CurrentPatternProcessor if the error is one that was unexpected. Signed-off-by: Gary Kim <gary@garykim.dev>
4 år sedan
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. }