github.com/gitbundle/modules@v0.0.0-20231025071548-85b91c5c3b01/markup/markdown/markdown_test.go (about)

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