forgejo/modules/markup/html_internal_test.go
Hester Gong ca905b82df
Append (comment) when a link points at a comment rather than the whole issue (#23734)
Close #23671 

For the feature mentioned above, this PR append ' (comment)' to the
rendered html if it is a hashcomment.
After the PR, type in the following

```
pull request from other repo:
http://localhost:3000/testOrg/testOrgRepo/pulls/2

pull request from this repo:
http://localhost:3000/aaa/testA/pulls/2

issue comment from this repo:
http://localhost:3000/aaa/testA/issues/1#issuecomment-18

http://localhost:3000/aaa/testA/pulls/2#issue-9

issue comment from other repo:
http://localhost:3000/testOrg/testOrgRepo/pulls/2#issuecomment-24

http://localhost:3000/testOrg/testOrgRepo/pulls/2#issue
```
Gives:
<img width="687" alt="截屏2023-03-27 13 53 06"
src="https://user-images.githubusercontent.com/17645053/227852387-2b218e0d-3468-4d90-ad81-d702ddd17fd2.png">



Other than the above feature, this PR also includes two other changes:

1 Right now, the render of links from file changed tab in pull request
might not be very proper, for example, if type in the following. (not
sure if this is an issue or design, if not an issue, I will revert the
changes). example on
[try.gitea.io](https://try.gitea.io/HesterG/testrepo/pulls/1)
```
https://try.gitea.io/HesterG/testrepo/pulls/1/files#issuecomment-162725
https://try.gitea.io/HesterG/testrepo/pulls/1/files
```
it will render the following 
<img width="899" alt="截屏2023-03-24 15 41 37"
src="https://user-images.githubusercontent.com/17645053/227456117-5eccedb7-9118-4540-929d-aee9a76de852.png">

In this PR, skip processing the link into a ref issue if it is a link
from files changed tab in pull request

After:
type in following
```
hash comment on files changed tab:
http://localhost:3000/testOrg/testOrgRepo/pulls/2/files#issuecomment-24

files changed link:
http://localhost:3000/testOrg/testOrgRepo/pulls/2/files
```
Gives

<img width="708" alt="截屏2023-03-27 22 09 02"
src="https://user-images.githubusercontent.com/17645053/227964273-5dc06c50-3713-489c-b05d-d95367d0ab0f.png">

2 Right now, after editing the comment area, there will not be tippys
attached to `ref-issue`; and no tippy attached on preview as well.
example:

https://user-images.githubusercontent.com/17645053/227850540-5ae34e2d-b1d7-4d0d-9726-7701bf825d1f.mov

In this PR, in frontend, make sure tippy is added after editing the
comment, and to the comment on preview tab
After:

https://user-images.githubusercontent.com/17645053/227853777-06f56b4c-1148-467c-b6f7-f79418e67504.mov
2023-04-03 16:02:57 +08:00

426 lines
13 KiB
Go

// Copyright 2018 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package markup
import (
"fmt"
"strconv"
"strings"
"testing"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
"github.com/stretchr/testify/assert"
)
const (
TestAppURL = "http://localhost:3000/"
TestOrgRepo = "gogits/gogs"
TestRepoURL = TestAppURL + TestOrgRepo + "/"
)
// externalIssueLink an HTML link to an alphanumeric-style issue
func externalIssueLink(baseURL, class, name string) string {
return link(util.URLJoin(baseURL, name), class, name)
}
// numericLink an HTML to a numeric-style issue
func numericIssueLink(baseURL, class string, index int, marker string) string {
return link(util.URLJoin(baseURL, strconv.Itoa(index)), class, fmt.Sprintf("%s%d", marker, index))
}
// link an HTML link
func link(href, class, contents string) string {
if class != "" {
class = " class=\"" + class + "\""
}
return fmt.Sprintf("<a href=\"%s\"%s>%s</a>", href, class, contents)
}
var numericMetas = map[string]string{
"format": "https://someurl.com/{user}/{repo}/{index}",
"user": "someUser",
"repo": "someRepo",
"style": IssueNameStyleNumeric,
}
var alphanumericMetas = map[string]string{
"format": "https://someurl.com/{user}/{repo}/{index}",
"user": "someUser",
"repo": "someRepo",
"style": IssueNameStyleAlphanumeric,
}
var regexpMetas = map[string]string{
"format": "https://someurl.com/{user}/{repo}/{index}",
"user": "someUser",
"repo": "someRepo",
"style": IssueNameStyleRegexp,
}
// these values should match the TestOrgRepo const above
var localMetas = map[string]string{
"user": "gogits",
"repo": "gogs",
}
func TestRender_IssueIndexPattern(t *testing.T) {
// numeric: render inputs without valid mentions
test := func(s string) {
testRenderIssueIndexPattern(t, s, s, &RenderContext{
Ctx: git.DefaultContext,
})
testRenderIssueIndexPattern(t, s, s, &RenderContext{
Ctx: git.DefaultContext,
Metas: numericMetas,
})
}
// should not render anything when there are no mentions
test("")
test("this is a test")
test("test 123 123 1234")
test("#")
test("# # #")
test("# 123")
test("#abcd")
test("test#1234")
test("#1234test")
test("#abcd")
test("test!1234")
test("!1234test")
test(" test !1234test")
test("/home/gitea/#1234")
test("/home/gitea/!1234")
// should not render issue mention without leading space
test("test#54321 issue")
// should not render issue mention without trailing space
test("test #54321issue")
}
func TestRender_IssueIndexPattern2(t *testing.T) {
setting.AppURL = TestAppURL
// numeric: render inputs with valid mentions
test := func(s, expectedFmt, marker string, indices ...int) {
var path, prefix string
isExternal := false
if marker == "!" {
path = "pulls"
prefix = "http://localhost:3000/someUser/someRepo/pulls/"
} else {
path = "issues"
prefix = "https://someurl.com/someUser/someRepo/"
isExternal = true
}
links := make([]interface{}, len(indices))
for i, index := range indices {
links[i] = numericIssueLink(util.URLJoin(TestRepoURL, path), "ref-issue", index, marker)
}
expectedNil := fmt.Sprintf(expectedFmt, links...)
testRenderIssueIndexPattern(t, s, expectedNil, &RenderContext{
Ctx: git.DefaultContext,
Metas: localMetas,
})
class := "ref-issue"
if isExternal {
class += " ref-external-issue"
}
for i, index := range indices {
links[i] = numericIssueLink(prefix, class, index, marker)
}
expectedNum := fmt.Sprintf(expectedFmt, links...)
testRenderIssueIndexPattern(t, s, expectedNum, &RenderContext{
Ctx: git.DefaultContext,
Metas: numericMetas,
})
}
// should render freestanding mentions
test("#1234 test", "%s test", "#", 1234)
test("test #8 issue", "test %s issue", "#", 8)
test("!1234 test", "%s test", "!", 1234)
test("test !8 issue", "test %s issue", "!", 8)
test("test issue #1234", "test issue %s", "#", 1234)
test("fixes issue #1234.", "fixes issue %s.", "#", 1234)
// should render mentions in parentheses / brackets
test("(#54321 issue)", "(%s issue)", "#", 54321)
test("[#54321 issue]", "[%s issue]", "#", 54321)
test("test (#9801 extra) issue", "test (%s extra) issue", "#", 9801)
test("test (!9801 extra) issue", "test (%s extra) issue", "!", 9801)
test("test (#1)", "test (%s)", "#", 1)
// should render multiple issue mentions in the same line
test("#54321 #1243", "%s %s", "#", 54321, 1243)
test("wow (#54321 #1243)", "wow (%s %s)", "#", 54321, 1243)
test("(#4)(#5)", "(%s)(%s)", "#", 4, 5)
test("#1 (#4321) test", "%s (%s) test", "#", 1, 4321)
// should render with :
test("#1234: test", "%s: test", "#", 1234)
test("wow (#54321: test)", "wow (%s: test)", "#", 54321)
}
func TestRender_IssueIndexPattern3(t *testing.T) {
setting.AppURL = TestAppURL
// alphanumeric: render inputs without valid mentions
test := func(s string) {
testRenderIssueIndexPattern(t, s, s, &RenderContext{
Ctx: git.DefaultContext,
Metas: alphanumericMetas,
})
}
test("")
test("this is a test")
test("test 123 123 1234")
test("#")
test("# 123")
test("#abcd")
test("test #123")
test("abc-1234") // issue prefix must be capital
test("ABc-1234") // issue prefix must be _all_ capital
test("ABCDEFGHIJK-1234") // the limit is 10 characters in the prefix
test("ABC1234") // dash is required
test("test ABC- test") // number is required
test("test -1234 test") // prefix is required
test("testABC-123 test") // leading space is required
test("test ABC-123test") // trailing space is required
test("ABC-0123") // no leading zero
}
func TestRender_IssueIndexPattern4(t *testing.T) {
setting.AppURL = TestAppURL
// alphanumeric: render inputs with valid mentions
test := func(s, expectedFmt string, names ...string) {
links := make([]interface{}, len(names))
for i, name := range names {
links[i] = externalIssueLink("https://someurl.com/someUser/someRepo/", "ref-issue ref-external-issue", name)
}
expected := fmt.Sprintf(expectedFmt, links...)
testRenderIssueIndexPattern(t, s, expected, &RenderContext{
Ctx: git.DefaultContext,
Metas: alphanumericMetas,
})
}
test("OTT-1234 test", "%s test", "OTT-1234")
test("test T-12 issue", "test %s issue", "T-12")
test("test issue ABCDEFGHIJ-1234567890", "test issue %s", "ABCDEFGHIJ-1234567890")
}
func TestRender_IssueIndexPattern5(t *testing.T) {
setting.AppURL = TestAppURL
// regexp: render inputs without valid mentions
test := func(s, expectedFmt, pattern string, ids, names []string) {
metas := regexpMetas
metas["regexp"] = pattern
links := make([]interface{}, len(ids))
for i, id := range ids {
links[i] = link(util.URLJoin("https://someurl.com/someUser/someRepo/", id), "ref-issue ref-external-issue", names[i])
}
expected := fmt.Sprintf(expectedFmt, links...)
testRenderIssueIndexPattern(t, s, expected, &RenderContext{
Ctx: git.DefaultContext,
Metas: metas,
})
}
test("abc ISSUE-123 def", "abc %s def",
"ISSUE-(\\d+)",
[]string{"123"},
[]string{"ISSUE-123"},
)
test("abc (ISSUE 123) def", "abc %s def",
"\\(ISSUE (\\d+)\\)",
[]string{"123"},
[]string{"(ISSUE 123)"},
)
test("abc ISSUE-123 def", "abc %s def",
"(ISSUE-(\\d+))",
[]string{"ISSUE-123"},
[]string{"ISSUE-123"},
)
testRenderIssueIndexPattern(t, "will not match", "will not match", &RenderContext{
Ctx: git.DefaultContext,
Metas: regexpMetas,
})
}
func testRenderIssueIndexPattern(t *testing.T, input, expected string, ctx *RenderContext) {
if ctx.URLPrefix == "" {
ctx.URLPrefix = TestAppURL
}
var buf strings.Builder
err := postProcess(ctx, []processor{issueIndexPatternProcessor}, strings.NewReader(input), &buf)
assert.NoError(t, err)
assert.Equal(t, expected, buf.String(), "input=%q", input)
}
func TestRender_AutoLink(t *testing.T) {
setting.AppURL = TestAppURL
test := func(input, expected string) {
var buffer strings.Builder
err := PostProcess(&RenderContext{
Ctx: git.DefaultContext,
URLPrefix: TestRepoURL,
Metas: localMetas,
}, strings.NewReader(input), &buffer)
assert.Equal(t, err, nil)
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer.String()))
buffer.Reset()
err = PostProcess(&RenderContext{
Ctx: git.DefaultContext,
URLPrefix: TestRepoURL,
Metas: localMetas,
IsWiki: true,
}, strings.NewReader(input), &buffer)
assert.Equal(t, err, nil)
assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer.String()))
}
// render valid issue URLs
test(util.URLJoin(TestRepoURL, "issues", "3333"),
numericIssueLink(util.URLJoin(TestRepoURL, "issues"), "ref-issue", 3333, "#"))
// render valid commit URLs
tmp := util.URLJoin(TestRepoURL, "commit", "d8a994ef243349f321568f9e36d5c3f444b99cae")
test(tmp, "<a href=\""+tmp+"\" class=\"commit\"><code class=\"nohighlight\">d8a994ef24</code></a>")
tmp += "#diff-2"
test(tmp, "<a href=\""+tmp+"\" class=\"commit\"><code class=\"nohighlight\">d8a994ef24 (diff-2)</code></a>")
// render other commit URLs
tmp = "https://external-link.gitea.io/go-gitea/gitea/commit/d8a994ef243349f321568f9e36d5c3f444b99cae#diff-2"
test(tmp, "<a href=\""+tmp+"\" class=\"commit\"><code class=\"nohighlight\">d8a994ef24 (diff-2)</code></a>")
}
func TestRender_FullIssueURLs(t *testing.T) {
setting.AppURL = TestAppURL
test := func(input, expected string) {
var result strings.Builder
err := postProcess(&RenderContext{
Ctx: git.DefaultContext,
URLPrefix: TestRepoURL,
Metas: localMetas,
}, []processor{fullIssuePatternProcessor}, strings.NewReader(input), &result)
assert.NoError(t, err)
assert.Equal(t, expected, result.String())
}
test("Here is a link https://git.osgeo.org/gogs/postgis/postgis/pulls/6",
"Here is a link https://git.osgeo.org/gogs/postgis/postgis/pulls/6")
test("Look here http://localhost:3000/person/repo/issues/4",
`Look here <a href="http://localhost:3000/person/repo/issues/4" class="ref-issue">person/repo#4</a>`)
test("http://localhost:3000/person/repo/issues/4#issuecomment-1234",
`<a href="http://localhost:3000/person/repo/issues/4#issuecomment-1234" class="ref-issue">person/repo#4 (comment)</a>`)
test("http://localhost:3000/gogits/gogs/issues/4",
`<a href="http://localhost:3000/gogits/gogs/issues/4" class="ref-issue">#4</a>`)
test("http://localhost:3000/gogits/gogs/issues/4 test",
`<a href="http://localhost:3000/gogits/gogs/issues/4" class="ref-issue">#4</a> test`)
test("http://localhost:3000/gogits/gogs/issues/4?a=1&b=2#comment-123 test",
`<a href="http://localhost:3000/gogits/gogs/issues/4?a=1&amp;b=2#comment-123" class="ref-issue">#4 (comment)</a> test`)
test("http://localhost:3000/testOrg/testOrgRepo/pulls/2/files#issuecomment-24",
"http://localhost:3000/testOrg/testOrgRepo/pulls/2/files#issuecomment-24")
test("http://localhost:3000/testOrg/testOrgRepo/pulls/2/files",
"http://localhost:3000/testOrg/testOrgRepo/pulls/2/files")
}
func TestRegExp_sha1CurrentPattern(t *testing.T) {
trueTestCases := []string{
"d8a994ef243349f321568f9e36d5c3f444b99cae",
"abcdefabcdefabcdefabcdefabcdefabcdefabcd",
"(abcdefabcdefabcdefabcdefabcdefabcdefabcd)",
"[abcdefabcdefabcdefabcdefabcdefabcdefabcd]",
"abcdefabcdefabcdefabcdefabcdefabcdefabcd.",
}
falseTestCases := []string{
"test",
"abcdefg",
"e59ff077-2d03-4e6b-964d-63fbaea81f",
"abcdefghijklmnopqrstuvwxyzabcdefghijklmn",
"abcdefghijklmnopqrstuvwxyzabcdefghijklmO",
}
for _, testCase := range trueTestCases {
assert.True(t, sha1CurrentPattern.MatchString(testCase))
}
for _, testCase := range falseTestCases {
assert.False(t, sha1CurrentPattern.MatchString(testCase))
}
}
func TestRegExp_anySHA1Pattern(t *testing.T) {
testCases := map[string][]string{
"https://github.com/jquery/jquery/blob/a644101ed04d0beacea864ce805e0c4f86ba1cd1/test/unit/event.js#L2703": {
"a644101ed04d0beacea864ce805e0c4f86ba1cd1",
"/test/unit/event.js",
"#L2703",
},
"https://github.com/jquery/jquery/blob/a644101ed04d0beacea864ce805e0c4f86ba1cd1/test/unit/event.js": {
"a644101ed04d0beacea864ce805e0c4f86ba1cd1",
"/test/unit/event.js",
"",
},
"https://github.com/jquery/jquery/commit/0705be475092aede1eddae01319ec931fb9c65fc": {
"0705be475092aede1eddae01319ec931fb9c65fc",
"",
"",
},
"https://github.com/jquery/jquery/tree/0705be475092aede1eddae01319ec931fb9c65fc/src": {
"0705be475092aede1eddae01319ec931fb9c65fc",
"/src",
"",
},
"https://try.gogs.io/gogs/gogs/commit/d8a994ef243349f321568f9e36d5c3f444b99cae#diff-2": {
"d8a994ef243349f321568f9e36d5c3f444b99cae",
"",
"#diff-2",
},
}
for k, v := range testCases {
assert.Equal(t, anySHA1Pattern.FindStringSubmatch(k)[1:], v)
}
}
func TestRegExp_shortLinkPattern(t *testing.T) {
trueTestCases := []string{
"[[stuff]]",
"[[]]",
"[[stuff|title=Difficult name with spaces*!]]",
}
falseTestCases := []string{
"test",
"abcdefg",
"[[]",
"[[",
"[]",
"]]",
"abcdefghijklmnopqrstuvwxyz",
}
for _, testCase := range trueTestCases {
assert.True(t, shortLinkPattern.MatchString(testCase))
}
for _, testCase := range falseTestCases {
assert.False(t, shortLinkPattern.MatchString(testCase))
}
}