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