mirror of
https://github.com/go-gitea/gitea.git
synced 2026-05-25 16:08:46 +09:00
Package-page markdown (READMEs, descriptions, release notes) was rendered as a plain document, so relative links and images resolved against the site root and 404'd. This renders it in the context of the package's linked repository instead, falling back to plain rendering when the package has no linked repo. For a README link `[usage](docs/usage.md)` in a package linked to `user/repo` (default branch `main`): | | Resolved link | |---|---| | Before | `/docs/usage.md` | | After | `/user/repo/src/branch/main/docs/usage.md` | For an npm monorepo package with `repository.directory: packages/foo`, an image `` resolves to `/user/repo/src/branch/main/packages/foo/logo.png`. Applied to every package content template that renders markdown: `cargo`, `chef`, `composer`, `npm`, `nuget`, `pub`, `pypi`. Links resolve against the repository default branch (metadata records no publish commit). Only the web package detail page is affected; registry API responses are unchanged. Note: as part of restructuring `npm.tmpl`, the package description and README now render as separate sections instead of the README replacing the description, matching the existing `cargo`/`composer`/`pub` layout. Signed-off-by: wxiaoguang <wxiaoguang@gmail.com> Signed-off-by: silverwind <me@silverwind.io> Co-authored-by: silverwind <me@silverwind.io> Co-authored-by: Claude (Opus 4.7) <noreply@anthropic.com> Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
257 lines
12 KiB
Go
257 lines
12 KiB
Go
// Copyright 2023 The Gitea Authors. All rights reserved.
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
package templates
|
|
|
|
import (
|
|
"context"
|
|
"html/template"
|
|
"os"
|
|
"strings"
|
|
"testing"
|
|
|
|
"code.gitea.io/gitea/models/issues"
|
|
"code.gitea.io/gitea/models/repo"
|
|
user_model "code.gitea.io/gitea/models/user"
|
|
"code.gitea.io/gitea/modules/markup"
|
|
"code.gitea.io/gitea/modules/reqctx"
|
|
"code.gitea.io/gitea/modules/setting"
|
|
"code.gitea.io/gitea/modules/test"
|
|
"code.gitea.io/gitea/modules/translation"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
)
|
|
|
|
func testInput() string {
|
|
s := ` space @mention-user<SPACE><SPACE>
|
|
/just/a/path.bin
|
|
https://example.com/file.bin
|
|
[local link](file.bin)
|
|
[remote link](https://example.com)
|
|
[[local link|file.bin]]
|
|
[[remote link|https://example.com]]
|
|

|
|

|
|
[[local image|image.jpg]]
|
|
[[remote link|https://example.com/image.jpg]]
|
|
https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash
|
|
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare
|
|
https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb
|
|
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit
|
|
:+1:
|
|
mail@domain.com
|
|
@mention-user test
|
|
#123
|
|
space<SPACE><SPACE>
|
|
`
|
|
return strings.ReplaceAll(s, "<SPACE>", " ")
|
|
}
|
|
|
|
func TestMain(m *testing.M) {
|
|
setting.Markdown.RenderOptionsComment.ShortIssuePattern = true
|
|
markup.Init(&markup.RenderHelperFuncs{
|
|
IsUsernameMentionable: func(ctx context.Context, username string) bool {
|
|
return username == "mention-user"
|
|
},
|
|
})
|
|
os.Exit(m.Run())
|
|
}
|
|
|
|
func newTestRenderUtils(t *testing.T) *RenderUtils {
|
|
ctx := reqctx.NewRequestContextForTest(t.Context())
|
|
ctx.SetContextValue(translation.ContextKey, &translation.MockLocale{})
|
|
return NewRenderUtils(ctx)
|
|
}
|
|
|
|
func TestRenderRepoComment(t *testing.T) {
|
|
mockRepo := &repo.Repository{
|
|
ID: 1, OwnerName: "user13", Name: "repo11",
|
|
Owner: &user_model.User{ID: 13, Name: "user13"},
|
|
Units: []*repo.RepoUnit{},
|
|
}
|
|
t.Run("RenderCommitBody", func(t *testing.T) {
|
|
defer test.MockVariableValue(&markup.RenderBehaviorForTesting.DisableAdditionalAttributes, true)()
|
|
type args struct {
|
|
msg string
|
|
}
|
|
tests := []struct {
|
|
name string
|
|
args args
|
|
want template.HTML
|
|
}{
|
|
{
|
|
name: "multiple lines",
|
|
args: args{
|
|
msg: "first line\nsecond line",
|
|
},
|
|
want: "second line",
|
|
},
|
|
{
|
|
name: "multiple lines with leading newlines",
|
|
args: args{
|
|
msg: "\n\n\n\nfirst line\nsecond line",
|
|
},
|
|
want: "second line",
|
|
},
|
|
{
|
|
name: "multiple lines with trailing newlines",
|
|
args: args{
|
|
msg: "first line\nsecond line\n\n\n",
|
|
},
|
|
want: "second line",
|
|
},
|
|
}
|
|
ut := newTestRenderUtils(t)
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
assert.Equalf(t, tt.want, ut.RenderCommitBody(tt.args.msg, mockRepo), "RenderCommitBody(%v, %v)", tt.args.msg, nil)
|
|
})
|
|
}
|
|
|
|
expected := `/just/a/path.bin
|
|
<a href="https://example.com/file.bin">https://example.com/file.bin</a>
|
|
[local link](file.bin)
|
|
[remote link](<a href="https://example.com">https://example.com</a>)
|
|
[[local link|file.bin]]
|
|
[[remote link|<a href="https://example.com">https://example.com</a>]]
|
|

|
|

|
|
[[local image|image.jpg]]
|
|
[[remote link|<a href="https://example.com/image.jpg">https://example.com/image.jpg</a>]]
|
|
<a href="https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash" class="compare"><code>88fc37a3c0...12fc37a3c0 (hash)</code></a>
|
|
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare
|
|
<a href="https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb" class="commit"><code>88fc37a3c0</code></a>
|
|
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit
|
|
<span class="emoji" aria-label="thumbs up">👍</span>
|
|
<a href="mailto:mail@domain.com">mail@domain.com</a>
|
|
<a href="/mention-user">@mention-user</a> test
|
|
<a href="/user13/repo11/issues/123" class="ref-issue">#123</a>
|
|
space`
|
|
assert.Equal(t, expected, string(newTestRenderUtils(t).RenderCommitBody(testInput(), mockRepo)))
|
|
})
|
|
|
|
t.Run("RenderCommitMessage", func(t *testing.T) {
|
|
expected := `space <a href="/mention-user" data-markdown-generated-content="">@mention-user</a> `
|
|
assert.EqualValues(t, expected, newTestRenderUtils(t).RenderCommitMessage(testInput(), mockRepo))
|
|
})
|
|
|
|
t.Run("RenderCommitMessageLinkSubject", func(t *testing.T) {
|
|
expected := `<a href="https://example.com/link" class="muted">space </a><a href="/mention-user" data-markdown-generated-content="">@mention-user</a>`
|
|
assert.EqualValues(t, expected, newTestRenderUtils(t).RenderCommitMessageLinkSubject(testInput(), "https://example.com/link", mockRepo))
|
|
})
|
|
|
|
t.Run("RenderIssueTitle", func(t *testing.T) {
|
|
defer test.MockVariableValue(&markup.RenderBehaviorForTesting.DisableAdditionalAttributes, true)()
|
|
expected := ` space @mention-user<SPACE><SPACE>
|
|
/just/a/path.bin
|
|
https://example.com/file.bin
|
|
[local link](file.bin)
|
|
[remote link](https://example.com)
|
|
[[local link|file.bin]]
|
|
[[remote link|https://example.com]]
|
|

|
|

|
|
[[local image|image.jpg]]
|
|
[[remote link|https://example.com/image.jpg]]
|
|
https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash
|
|
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare
|
|
https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb
|
|
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit
|
|
<span class="emoji" aria-label="thumbs up">👍</span>
|
|
mail@domain.com
|
|
@mention-user test
|
|
<a href="/user13/repo11/issues/123" class="ref-issue">#123</a>
|
|
space<SPACE><SPACE>
|
|
`
|
|
expected = strings.ReplaceAll(expected, "<SPACE>", " ")
|
|
assert.Equal(t, expected, string(newTestRenderUtils(t).RenderIssueTitle(testInput(), mockRepo)))
|
|
})
|
|
}
|
|
|
|
func TestRenderMarkdownToHtml(t *testing.T) {
|
|
defer test.MockVariableValue(&markup.RenderBehaviorForTesting.DisableAdditionalAttributes, true)()
|
|
expected := `<p>space <a href="/mention-user" rel="nofollow">@mention-user</a><br/>
|
|
/just/a/path.bin
|
|
<a href="https://example.com/file.bin" rel="nofollow">https://example.com/file.bin</a>
|
|
<a href="/file.bin" rel="nofollow">local link</a>
|
|
<a href="https://example.com" rel="nofollow">remote link</a>
|
|
<a href="/file.bin" rel="nofollow">local link</a>
|
|
<a href="https://example.com" rel="nofollow">remote link</a>
|
|
<a href="/image.jpg" target="_blank" rel="nofollow noopener"><img src="/image.jpg" alt="local image"/></a>
|
|
<a href="https://example.com/image.jpg" target="_blank" rel="nofollow noopener"><img src="https://example.com/image.jpg" alt="remote image"/></a>
|
|
<a href="/image.jpg" rel="nofollow"><img src="/image.jpg" title="local image" alt="local image"/></a>
|
|
<a href="https://example.com/image.jpg" rel="nofollow"><img src="https://example.com/image.jpg" title="remote link" alt="remote link"/></a>
|
|
<a href="https://example.com/user/repo/compare/88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb#hash" rel="nofollow"><code>88fc37a3c0...12fc37a3c0 (hash)</code></a>
|
|
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb...12fc37a3c0a4dda553bdcfc80c178a58247f42fb pare
|
|
<a href="https://example.com/user/repo/commit/88fc37a3c0a4dda553bdcfc80c178a58247f42fb" rel="nofollow"><code>88fc37a3c0</code></a>
|
|
com 88fc37a3c0a4dda553bdcfc80c178a58247f42fb mit
|
|
<span class="emoji" aria-label="thumbs up">👍</span>
|
|
<a href="mailto:mail@domain.com" rel="nofollow">mail@domain.com</a>
|
|
<a href="/mention-user" rel="nofollow">@mention-user</a> test
|
|
#123
|
|
space</p>
|
|
`
|
|
assert.Equal(t, expected, string(newTestRenderUtils(t).MarkdownToHtml(testInput())))
|
|
}
|
|
|
|
func TestRenderPackageMarkdown(t *testing.T) {
|
|
defer test.MockVariableValue(&markup.RenderBehaviorForTesting.DisableAdditionalAttributes, true)()
|
|
mockRepo := &repo.Repository{
|
|
ID: 1, OwnerName: "user13", Name: "repo11", DefaultBranch: "main",
|
|
Owner: &user_model.User{ID: 13, Name: "user13"},
|
|
Units: []*repo.RepoUnit{},
|
|
}
|
|
ut := newTestRenderUtils(t)
|
|
|
|
t.Run("LinkedRepoWithDirectory", func(t *testing.T) {
|
|
rendered := ut.RenderPackageMarkdown("[docs](docs/getting-started.md)\n", mockRepo, "pkg-subdir")
|
|
expected := `<div class="markup markdown"><p><a href="/user13/repo11/src/branch/main/pkg-subdir/docs/getting-started.md" rel="nofollow">docs</a>
|
|
<a href="/user13/repo11/src/branch/main/pkg-subdir/logo.png" target="_blank" rel="nofollow noopener"><img src="/user13/repo11/media/branch/main/pkg-subdir/logo.png" alt="logo"/></a></p>
|
|
</div>`
|
|
assert.Equal(t, expected, strings.TrimSpace(string(rendered)))
|
|
})
|
|
|
|
t.Run("LinkedRepoWithEmptyDirectory", func(t *testing.T) {
|
|
rendered := ut.RenderPackageMarkdown("[docs](docs/getting-started.md)", mockRepo, "")
|
|
expected := `<div class="markup markdown"><p><a href="/user13/repo11/src/branch/main/docs/getting-started.md" rel="nofollow">docs</a></p>
|
|
</div>`
|
|
assert.Equal(t, expected, strings.TrimSpace(string(rendered)))
|
|
})
|
|
|
|
t.Run("UnlinkedRepo", func(t *testing.T) {
|
|
rendered := ut.RenderPackageMarkdown("[docs](docs/getting-started.md)", nil, "pkg-subdir")
|
|
expected := `<div class="markup markdown"><p><a href="/docs/getting-started.md" rel="nofollow">docs</a></p>
|
|
</div>`
|
|
assert.Equal(t, expected, strings.TrimSpace(string(rendered)))
|
|
})
|
|
}
|
|
|
|
func TestRenderLabels(t *testing.T) {
|
|
ut := newTestRenderUtils(t)
|
|
label := &issues.Label{ID: 123, Name: "label-name", Color: "label-color"}
|
|
issue := &issues.Issue{}
|
|
expected := `/owner/repo/issues?labels=123`
|
|
assert.Contains(t, ut.RenderLabels([]*issues.Label{label}, "/owner/repo", issue), expected)
|
|
|
|
label = &issues.Label{ID: 123, Name: "label-name", Color: "label-color"}
|
|
issue = &issues.Issue{IsPull: true}
|
|
expected = `/owner/repo/pulls?labels=123`
|
|
assert.Contains(t, ut.RenderLabels([]*issues.Label{label}, "/owner/repo", issue), expected)
|
|
|
|
expectedLabel := `<span class="ui label " style="color: #fff !important; background-color: label-color !important;" data-tooltip-content title=""><span class="gt-ellipsis">label-name</span></span>`
|
|
assert.Equal(t, expectedLabel, string(ut.RenderLabel(label)))
|
|
|
|
label = &issues.Label{ID: 123, Name: "</>", Exclusive: true}
|
|
expectedLabel = `<span class="ui label scope-parent" data-tooltip-content title=""><div class="ui label scope-left" style="color: #fff !important; background-color: #000000 !important"><</div><div class="ui label scope-right" style="color: #fff !important; background-color: #000000 !important">></div></span>`
|
|
assert.Equal(t, expectedLabel, string(ut.RenderLabel(label)))
|
|
label = &issues.Label{ID: 123, Name: "</>", Exclusive: true, ExclusiveOrder: 1}
|
|
expectedLabel = `<span class="ui label scope-parent" data-tooltip-content title=""><div class="ui label scope-left" style="color: #fff !important; background-color: #000000 !important"><</div><div class="ui label scope-middle" style="color: #fff !important; background-color: #000000 !important">></div><div class="ui label scope-right">1</div></span>`
|
|
assert.Equal(t, expectedLabel, string(ut.RenderLabel(label)))
|
|
}
|
|
|
|
func TestUserMention(t *testing.T) {
|
|
markup.RenderBehaviorForTesting.DisableAdditionalAttributes = true
|
|
rendered := newTestRenderUtils(t).MarkdownToHtml("@no-such-user @mention-user @mention-user")
|
|
assert.Equal(t, `<p>@no-such-user <a href="/mention-user" rel="nofollow">@mention-user</a> <a href="/mention-user" rel="nofollow">@mention-user</a></p>`, strings.TrimSpace(string(rendered)))
|
|
}
|