github.com/gitbundle/modules@v0.0.0-20231025071548-85b91c5c3b01/markup/html_internal_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 markup 7 8 import ( 9 "fmt" 10 "strconv" 11 "strings" 12 "testing" 13 14 "github.com/gitbundle/modules/setting" 15 "github.com/gitbundle/modules/util" 16 17 "github.com/stretchr/testify/assert" 18 ) 19 20 const ( 21 TestAppURL = "http://localhost:3000/" 22 TestOrgRepo = "gogits/gogs" 23 TestRepoURL = TestAppURL + TestOrgRepo + "/" 24 ) 25 26 // externalIssueLink an HTML link to an alphanumeric-style issue 27 func externalIssueLink(baseURL, class, name string) string { 28 return link(util.URLJoin(baseURL, name), class, name) 29 } 30 31 // numericLink an HTML to a numeric-style issue 32 func numericIssueLink(baseURL, class string, index int, marker string) string { 33 return link(util.URLJoin(baseURL, strconv.Itoa(index)), class, fmt.Sprintf("%s%d", marker, index)) 34 } 35 36 // link an HTML link 37 func link(href, class, contents string) string { 38 if class != "" { 39 class = " class=\"" + class + "\"" 40 } 41 42 return fmt.Sprintf("<a href=\"%s\"%s>%s</a>", href, class, contents) 43 } 44 45 var numericMetas = map[string]string{ 46 "format": "https://someurl.com/{user}/{repo}/{index}", 47 "user": "someUser", 48 "repo": "someRepo", 49 "style": IssueNameStyleNumeric, 50 } 51 52 var alphanumericMetas = map[string]string{ 53 "format": "https://someurl.com/{user}/{repo}/{index}", 54 "user": "someUser", 55 "repo": "someRepo", 56 "style": IssueNameStyleAlphanumeric, 57 } 58 59 var regexpMetas = map[string]string{ 60 "format": "https://someurl.com/{user}/{repo}/{index}", 61 "user": "someUser", 62 "repo": "someRepo", 63 "style": IssueNameStyleRegexp, 64 } 65 66 // these values should match the TestOrgRepo const above 67 var localMetas = map[string]string{ 68 "user": "gogits", 69 "repo": "gogs", 70 } 71 72 func TestRender_IssueIndexPattern(t *testing.T) { 73 // numeric: render inputs without valid mentions 74 test := func(s string) { 75 testRenderIssueIndexPattern(t, s, s, &RenderContext{}) 76 testRenderIssueIndexPattern(t, s, s, &RenderContext{Metas: numericMetas}) 77 } 78 79 // should not render anything when there are no mentions 80 test("") 81 test("this is a test") 82 test("test 123 123 1234") 83 test("#") 84 test("# # #") 85 test("# 123") 86 test("#abcd") 87 test("test#1234") 88 test("#1234test") 89 test("#abcd") 90 test("test!1234") 91 test("!1234test") 92 test(" test !1234test") 93 test("/home/gitea/#1234") 94 test("/home/gitea/!1234") 95 96 // should not render issue mention without leading space 97 test("test#54321 issue") 98 99 // should not render issue mention without trailing space 100 test("test #54321issue") 101 } 102 103 func TestRender_IssueIndexPattern2(t *testing.T) { 104 setting.AppURL = TestAppURL 105 106 // numeric: render inputs with valid mentions 107 test := func(s, expectedFmt, marker string, indices ...int) { 108 var path, prefix string 109 isExternal := false 110 if marker == "!" { 111 path = "pulls" 112 prefix = "http://localhost:3000/someUser/someRepo/pulls/" 113 } else { 114 path = "issues" 115 prefix = "https://someurl.com/someUser/someRepo/" 116 isExternal = true 117 } 118 119 links := make([]interface{}, len(indices)) 120 for i, index := range indices { 121 links[i] = numericIssueLink(util.URLJoin(TestRepoURL, path), "ref-issue", index, marker) 122 } 123 expectedNil := fmt.Sprintf(expectedFmt, links...) 124 testRenderIssueIndexPattern(t, s, expectedNil, &RenderContext{Metas: localMetas}) 125 126 class := "ref-issue" 127 if isExternal { 128 class += " ref-external-issue" 129 } 130 131 for i, index := range indices { 132 links[i] = numericIssueLink(prefix, class, index, marker) 133 } 134 expectedNum := fmt.Sprintf(expectedFmt, links...) 135 testRenderIssueIndexPattern(t, s, expectedNum, &RenderContext{Metas: numericMetas}) 136 } 137 138 // should render freestanding mentions 139 test("#1234 test", "%s test", "#", 1234) 140 test("test #8 issue", "test %s issue", "#", 8) 141 test("!1234 test", "%s test", "!", 1234) 142 test("test !8 issue", "test %s issue", "!", 8) 143 test("test issue #1234", "test issue %s", "#", 1234) 144 test("fixes issue #1234.", "fixes issue %s.", "#", 1234) 145 146 // should render mentions in parentheses / brackets 147 test("(#54321 issue)", "(%s issue)", "#", 54321) 148 test("[#54321 issue]", "[%s issue]", "#", 54321) 149 test("test (#9801 extra) issue", "test (%s extra) issue", "#", 9801) 150 test("test (!9801 extra) issue", "test (%s extra) issue", "!", 9801) 151 test("test (#1)", "test (%s)", "#", 1) 152 153 // should render multiple issue mentions in the same line 154 test("#54321 #1243", "%s %s", "#", 54321, 1243) 155 test("wow (#54321 #1243)", "wow (%s %s)", "#", 54321, 1243) 156 test("(#4)(#5)", "(%s)(%s)", "#", 4, 5) 157 test("#1 (#4321) test", "%s (%s) test", "#", 1, 4321) 158 159 // should render with : 160 test("#1234: test", "%s: test", "#", 1234) 161 test("wow (#54321: test)", "wow (%s: test)", "#", 54321) 162 } 163 164 func TestRender_IssueIndexPattern3(t *testing.T) { 165 setting.AppURL = TestAppURL 166 167 // alphanumeric: render inputs without valid mentions 168 test := func(s string) { 169 testRenderIssueIndexPattern(t, s, s, &RenderContext{Metas: alphanumericMetas}) 170 } 171 test("") 172 test("this is a test") 173 test("test 123 123 1234") 174 test("#") 175 test("# 123") 176 test("#abcd") 177 test("test #123") 178 test("abc-1234") // issue prefix must be capital 179 test("ABc-1234") // issue prefix must be _all_ capital 180 test("ABCDEFGHIJK-1234") // the limit is 10 characters in the prefix 181 test("ABC1234") // dash is required 182 test("test ABC- test") // number is required 183 test("test -1234 test") // prefix is required 184 test("testABC-123 test") // leading space is required 185 test("test ABC-123test") // trailing space is required 186 test("ABC-0123") // no leading zero 187 } 188 189 func TestRender_IssueIndexPattern4(t *testing.T) { 190 setting.AppURL = TestAppURL 191 192 // alphanumeric: render inputs with valid mentions 193 test := func(s, expectedFmt string, names ...string) { 194 links := make([]interface{}, len(names)) 195 for i, name := range names { 196 links[i] = externalIssueLink("https://someurl.com/someUser/someRepo/", "ref-issue ref-external-issue", name) 197 } 198 expected := fmt.Sprintf(expectedFmt, links...) 199 testRenderIssueIndexPattern(t, s, expected, &RenderContext{Metas: alphanumericMetas}) 200 } 201 test("OTT-1234 test", "%s test", "OTT-1234") 202 test("test T-12 issue", "test %s issue", "T-12") 203 test("test issue ABCDEFGHIJ-1234567890", "test issue %s", "ABCDEFGHIJ-1234567890") 204 } 205 206 func TestRender_IssueIndexPattern5(t *testing.T) { 207 setting.AppURL = TestAppURL 208 209 // regexp: render inputs without valid mentions 210 test := func(s, expectedFmt, pattern string, ids, names []string) { 211 metas := regexpMetas 212 metas["regexp"] = pattern 213 links := make([]interface{}, len(ids)) 214 for i, id := range ids { 215 links[i] = link(util.URLJoin("https://someurl.com/someUser/someRepo/", id), "ref-issue ref-external-issue", names[i]) 216 } 217 218 expected := fmt.Sprintf(expectedFmt, links...) 219 testRenderIssueIndexPattern(t, s, expected, &RenderContext{Metas: metas}) 220 } 221 222 test("abc ISSUE-123 def", "abc %s def", 223 "ISSUE-(\\d+)", 224 []string{"123"}, 225 []string{"ISSUE-123"}, 226 ) 227 228 test("abc (ISSUE 123) def", "abc %s def", 229 "\\(ISSUE (\\d+)\\)", 230 []string{"123"}, 231 []string{"(ISSUE 123)"}, 232 ) 233 234 test("abc ISSUE-123 def", "abc %s def", 235 "(ISSUE-(\\d+))", 236 []string{"ISSUE-123"}, 237 []string{"ISSUE-123"}, 238 ) 239 240 testRenderIssueIndexPattern(t, "will not match", "will not match", &RenderContext{Metas: regexpMetas}) 241 } 242 243 func testRenderIssueIndexPattern(t *testing.T, input, expected string, ctx *RenderContext) { 244 if ctx.URLPrefix == "" { 245 ctx.URLPrefix = TestAppURL 246 } 247 248 var buf strings.Builder 249 err := postProcess(ctx, []processor{issueIndexPatternProcessor}, strings.NewReader(input), &buf) 250 assert.NoError(t, err) 251 assert.Equal(t, expected, buf.String(), "input=%q", input) 252 } 253 254 func TestRender_AutoLink(t *testing.T) { 255 setting.AppURL = TestAppURL 256 257 test := func(input, expected string) { 258 var buffer strings.Builder 259 err := PostProcess(&RenderContext{ 260 URLPrefix: TestRepoURL, 261 Metas: localMetas, 262 }, strings.NewReader(input), &buffer) 263 assert.Equal(t, err, nil) 264 assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer.String())) 265 266 buffer.Reset() 267 err = PostProcess(&RenderContext{ 268 URLPrefix: TestRepoURL, 269 Metas: localMetas, 270 IsWiki: true, 271 }, strings.NewReader(input), &buffer) 272 assert.Equal(t, err, nil) 273 assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer.String())) 274 } 275 276 // render valid issue URLs 277 test(util.URLJoin(TestRepoURL, "issues", "3333"), 278 numericIssueLink(util.URLJoin(TestRepoURL, "issues"), "ref-issue", 3333, "#")) 279 280 // render valid commit URLs 281 tmp := util.URLJoin(TestRepoURL, "commit", "d8a994ef243349f321568f9e36d5c3f444b99cae") 282 test(tmp, "<a href=\""+tmp+"\" class=\"commit\"><code class=\"nohighlight\">d8a994ef24</code></a>") 283 tmp += "#diff-2" 284 test(tmp, "<a href=\""+tmp+"\" class=\"commit\"><code class=\"nohighlight\">d8a994ef24 (diff-2)</code></a>") 285 286 // render other commit URLs 287 tmp = "https://external-link.gitea.io/go-gitea/gitea/commit/d8a994ef243349f321568f9e36d5c3f444b99cae#diff-2" 288 test(tmp, "<a href=\""+tmp+"\" class=\"commit\"><code class=\"nohighlight\">d8a994ef24 (diff-2)</code></a>") 289 } 290 291 func TestRender_FullIssueURLs(t *testing.T) { 292 setting.AppURL = TestAppURL 293 294 test := func(input, expected string) { 295 var result strings.Builder 296 err := postProcess(&RenderContext{ 297 URLPrefix: TestRepoURL, 298 Metas: localMetas, 299 }, []processor{fullIssuePatternProcessor}, strings.NewReader(input), &result) 300 assert.NoError(t, err) 301 assert.Equal(t, expected, result.String()) 302 } 303 test("Here is a link https://git.osgeo.org/gogs/postgis/postgis/pulls/6", 304 "Here is a link https://git.osgeo.org/gogs/postgis/postgis/pulls/6") 305 test("Look here http://localhost:3000/person/repo/issues/4", 306 `Look here <a href="http://localhost:3000/person/repo/issues/4" class="ref-issue">person/repo#4</a>`) 307 test("http://localhost:3000/person/repo/issues/4#issuecomment-1234", 308 `<a href="http://localhost:3000/person/repo/issues/4#issuecomment-1234" class="ref-issue">person/repo#4</a>`) 309 test("http://localhost:3000/gogits/gogs/issues/4", 310 `<a href="http://localhost:3000/gogits/gogs/issues/4" class="ref-issue">#4</a>`) 311 test("http://localhost:3000/gogits/gogs/issues/4 test", 312 `<a href="http://localhost:3000/gogits/gogs/issues/4" class="ref-issue">#4</a> test`) 313 test("http://localhost:3000/gogits/gogs/issues/4?a=1&b=2#comment-123 test", 314 `<a href="http://localhost:3000/gogits/gogs/issues/4?a=1&b=2#comment-123" class="ref-issue">#4</a> test`) 315 } 316 317 func TestRegExp_sha1CurrentPattern(t *testing.T) { 318 trueTestCases := []string{ 319 "d8a994ef243349f321568f9e36d5c3f444b99cae", 320 "abcdefabcdefabcdefabcdefabcdefabcdefabcd", 321 "(abcdefabcdefabcdefabcdefabcdefabcdefabcd)", 322 "[abcdefabcdefabcdefabcdefabcdefabcdefabcd]", 323 "abcdefabcdefabcdefabcdefabcdefabcdefabcd.", 324 } 325 falseTestCases := []string{ 326 "test", 327 "abcdefg", 328 "e59ff077-2d03-4e6b-964d-63fbaea81f", 329 "abcdefghijklmnopqrstuvwxyzabcdefghijklmn", 330 "abcdefghijklmnopqrstuvwxyzabcdefghijklmO", 331 } 332 333 for _, testCase := range trueTestCases { 334 assert.True(t, sha1CurrentPattern.MatchString(testCase)) 335 } 336 for _, testCase := range falseTestCases { 337 assert.False(t, sha1CurrentPattern.MatchString(testCase)) 338 } 339 } 340 341 func TestRegExp_anySHA1Pattern(t *testing.T) { 342 testCases := map[string][]string{ 343 "https://github.com/jquery/jquery/blob/a644101ed04d0beacea864ce805e0c4f86ba1cd1/test/unit/event.js#L2703": { 344 "a644101ed04d0beacea864ce805e0c4f86ba1cd1", 345 "/test/unit/event.js", 346 "#L2703", 347 }, 348 "https://github.com/jquery/jquery/blob/a644101ed04d0beacea864ce805e0c4f86ba1cd1/test/unit/event.js": { 349 "a644101ed04d0beacea864ce805e0c4f86ba1cd1", 350 "/test/unit/event.js", 351 "", 352 }, 353 "https://github.com/jquery/jquery/commit/0705be475092aede1eddae01319ec931fb9c65fc": { 354 "0705be475092aede1eddae01319ec931fb9c65fc", 355 "", 356 "", 357 }, 358 "https://github.com/jquery/jquery/tree/0705be475092aede1eddae01319ec931fb9c65fc/src": { 359 "0705be475092aede1eddae01319ec931fb9c65fc", 360 "/src", 361 "", 362 }, 363 "https://try.gogs.io/gogs/gogs/commit/d8a994ef243349f321568f9e36d5c3f444b99cae#diff-2": { 364 "d8a994ef243349f321568f9e36d5c3f444b99cae", 365 "", 366 "#diff-2", 367 }, 368 } 369 370 for k, v := range testCases { 371 assert.Equal(t, anySHA1Pattern.FindStringSubmatch(k)[1:], v) 372 } 373 } 374 375 func TestRegExp_shortLinkPattern(t *testing.T) { 376 trueTestCases := []string{ 377 "[[stuff]]", 378 "[[]]", 379 "[[stuff|title=Difficult name with spaces*!]]", 380 } 381 falseTestCases := []string{ 382 "test", 383 "abcdefg", 384 "[[]", 385 "[[", 386 "[]", 387 "]]", 388 "abcdefghijklmnopqrstuvwxyz", 389 } 390 391 for _, testCase := range trueTestCases { 392 assert.True(t, shortLinkPattern.MatchString(testCase)) 393 } 394 for _, testCase := range falseTestCases { 395 assert.False(t, shortLinkPattern.MatchString(testCase)) 396 } 397 }