loading up the forgejo repo on tangled to test page performance
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Merge pull request 'Reintroduce tests of updated pull request icons' (#4598) from bramh/forgejo:update-pr-icons into forgejo

Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/4598
Reviewed-by: 0ko <0ko@noreply.codeberg.org>
Reviewed-by: Gusted <gusted@noreply.codeberg.org>
Reviewed-by: Earl Warren <earl-warren@noreply.codeberg.org>

+406 -25
+257
tests/integration/pull_icon_test.go
··· 1 + // Copyright 2024 The Forgejo Authors. All rights reserved. 2 + // SPDX-License-Identifier: GPL-3.0-or-later 3 + 4 + package integration 5 + 6 + import ( 7 + "context" 8 + "fmt" 9 + "net/http" 10 + "net/url" 11 + "strings" 12 + "testing" 13 + "time" 14 + 15 + "code.gitea.io/gitea/models/db" 16 + issues_model "code.gitea.io/gitea/models/issues" 17 + repo_model "code.gitea.io/gitea/models/repo" 18 + unit_model "code.gitea.io/gitea/models/unit" 19 + "code.gitea.io/gitea/models/unittest" 20 + user_model "code.gitea.io/gitea/models/user" 21 + "code.gitea.io/gitea/modules/git" 22 + issue_service "code.gitea.io/gitea/services/issue" 23 + pull_service "code.gitea.io/gitea/services/pull" 24 + files_service "code.gitea.io/gitea/services/repository/files" 25 + "code.gitea.io/gitea/tests" 26 + 27 + "github.com/PuerkitoBio/goquery" 28 + "github.com/stretchr/testify/assert" 29 + "github.com/stretchr/testify/require" 30 + ) 31 + 32 + func TestPullRequestIcons(t *testing.T) { 33 + onGiteaRun(t, func(t *testing.T, u *url.URL) { 34 + user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 1}) 35 + repo, _, f := CreateDeclarativeRepo(t, user, "pr-icons", []unit_model.Type{unit_model.TypeCode, unit_model.TypePullRequests}, nil, nil) 36 + defer f() 37 + 38 + session := loginUser(t, user.LoginName) 39 + 40 + // Individual PRs 41 + t.Run("Open", func(t *testing.T) { 42 + defer tests.PrintCurrentTest(t)() 43 + 44 + pull := createOpenPullRequest(db.DefaultContext, t, user, repo) 45 + testPullRequestIcon(t, session, pull, "green", "octicon-git-pull-request") 46 + }) 47 + 48 + t.Run("WIP (Open)", func(t *testing.T) { 49 + defer tests.PrintCurrentTest(t)() 50 + 51 + pull := createOpenWipPullRequest(db.DefaultContext, t, user, repo) 52 + testPullRequestIcon(t, session, pull, "grey", "octicon-git-pull-request-draft") 53 + }) 54 + 55 + t.Run("Closed", func(t *testing.T) { 56 + defer tests.PrintCurrentTest(t)() 57 + 58 + pull := createClosedPullRequest(db.DefaultContext, t, user, repo) 59 + testPullRequestIcon(t, session, pull, "red", "octicon-git-pull-request-closed") 60 + }) 61 + 62 + t.Run("WIP (Closed)", func(t *testing.T) { 63 + defer tests.PrintCurrentTest(t)() 64 + 65 + pull := createClosedWipPullRequest(db.DefaultContext, t, user, repo) 66 + testPullRequestIcon(t, session, pull, "red", "octicon-git-pull-request-closed") 67 + }) 68 + 69 + t.Run("Merged", func(t *testing.T) { 70 + defer tests.PrintCurrentTest(t)() 71 + 72 + pull := createMergedPullRequest(db.DefaultContext, t, user, repo) 73 + testPullRequestIcon(t, session, pull, "purple", "octicon-git-merge") 74 + }) 75 + 76 + // List 77 + req := NewRequest(t, "GET", repo.HTMLURL()+"/pulls?state=all") 78 + resp := session.MakeRequest(t, req, http.StatusOK) 79 + doc := NewHTMLParser(t, resp.Body) 80 + 81 + t.Run("List Open", func(t *testing.T) { 82 + defer tests.PrintCurrentTest(t)() 83 + 84 + testPullRequestListIcon(t, doc, "open", "green", "octicon-git-pull-request") 85 + }) 86 + 87 + t.Run("List WIP (Open)", func(t *testing.T) { 88 + defer tests.PrintCurrentTest(t)() 89 + 90 + testPullRequestListIcon(t, doc, "open-wip", "grey", "octicon-git-pull-request-draft") 91 + }) 92 + 93 + t.Run("List Closed", func(t *testing.T) { 94 + defer tests.PrintCurrentTest(t)() 95 + 96 + testPullRequestListIcon(t, doc, "closed", "red", "octicon-git-pull-request-closed") 97 + }) 98 + 99 + t.Run("List Closed (WIP)", func(t *testing.T) { 100 + defer tests.PrintCurrentTest(t)() 101 + 102 + testPullRequestListIcon(t, doc, "closed-wip", "red", "octicon-git-pull-request-closed") 103 + }) 104 + 105 + t.Run("List Merged", func(t *testing.T) { 106 + defer tests.PrintCurrentTest(t)() 107 + 108 + testPullRequestListIcon(t, doc, "merged", "purple", "octicon-git-merge") 109 + }) 110 + }) 111 + } 112 + 113 + func testPullRequestIcon(t *testing.T, session *TestSession, pr *issues_model.PullRequest, expectedColor, expectedIcon string) { 114 + req := NewRequest(t, "GET", pr.Issue.HTMLURL()) 115 + resp := session.MakeRequest(t, req, http.StatusOK) 116 + doc := NewHTMLParser(t, resp.Body) 117 + doc.AssertElement(t, fmt.Sprintf("div.issue-state-label.%s > svg.%s", expectedColor, expectedIcon), true) 118 + 119 + req = NewRequest(t, "GET", pr.BaseRepo.HTMLURL()+"/branches") 120 + resp = session.MakeRequest(t, req, http.StatusOK) 121 + doc = NewHTMLParser(t, resp.Body) 122 + doc.AssertElement(t, fmt.Sprintf(`a[href="/%s/pulls/%d"].%s > svg.%s`, pr.BaseRepo.FullName(), pr.Issue.Index, expectedColor, expectedIcon), true) 123 + } 124 + 125 + func testPullRequestListIcon(t *testing.T, doc *HTMLDoc, name, expectedColor, expectedIcon string) { 126 + sel := doc.doc.Find("div#issue-list > div.flex-item"). 127 + FilterFunction(func(_ int, selection *goquery.Selection) bool { 128 + return selection.Find(fmt.Sprintf(`div.flex-item-icon > svg.%s.%s`, expectedColor, expectedIcon)).Length() == 1 && 129 + strings.HasSuffix(selection.Find("a.issue-title").Text(), name) 130 + }) 131 + 132 + assert.Equal(t, 1, sel.Length()) 133 + } 134 + 135 + func createOpenPullRequest(ctx context.Context, t *testing.T, user *user_model.User, repo *repo_model.Repository) *issues_model.PullRequest { 136 + pull := createPullRequest(t, user, repo, "open") 137 + 138 + assert.False(t, pull.Issue.IsClosed) 139 + assert.False(t, pull.HasMerged) 140 + assert.False(t, pull.IsWorkInProgress(ctx)) 141 + 142 + return pull 143 + } 144 + 145 + func createOpenWipPullRequest(ctx context.Context, t *testing.T, user *user_model.User, repo *repo_model.Repository) *issues_model.PullRequest { 146 + pull := createPullRequest(t, user, repo, "open-wip") 147 + 148 + err := issue_service.ChangeTitle(ctx, pull.Issue, user, "WIP: "+pull.Issue.Title) 149 + require.NoError(t, err) 150 + 151 + assert.False(t, pull.Issue.IsClosed) 152 + assert.False(t, pull.HasMerged) 153 + assert.True(t, pull.IsWorkInProgress(ctx)) 154 + 155 + return pull 156 + } 157 + 158 + func createClosedPullRequest(ctx context.Context, t *testing.T, user *user_model.User, repo *repo_model.Repository) *issues_model.PullRequest { 159 + pull := createPullRequest(t, user, repo, "closed") 160 + 161 + err := issue_service.ChangeStatus(ctx, pull.Issue, user, "", true) 162 + require.NoError(t, err) 163 + 164 + assert.True(t, pull.Issue.IsClosed) 165 + assert.False(t, pull.HasMerged) 166 + assert.False(t, pull.IsWorkInProgress(ctx)) 167 + 168 + return pull 169 + } 170 + 171 + func createClosedWipPullRequest(ctx context.Context, t *testing.T, user *user_model.User, repo *repo_model.Repository) *issues_model.PullRequest { 172 + pull := createPullRequest(t, user, repo, "closed-wip") 173 + 174 + err := issue_service.ChangeTitle(ctx, pull.Issue, user, "WIP: "+pull.Issue.Title) 175 + require.NoError(t, err) 176 + 177 + err = issue_service.ChangeStatus(ctx, pull.Issue, user, "", true) 178 + require.NoError(t, err) 179 + 180 + assert.True(t, pull.Issue.IsClosed) 181 + assert.False(t, pull.HasMerged) 182 + assert.True(t, pull.IsWorkInProgress(ctx)) 183 + 184 + return pull 185 + } 186 + 187 + func createMergedPullRequest(ctx context.Context, t *testing.T, user *user_model.User, repo *repo_model.Repository) *issues_model.PullRequest { 188 + pull := createPullRequest(t, user, repo, "merged") 189 + 190 + gitRepo, err := git.OpenRepository(ctx, repo.RepoPath()) 191 + defer gitRepo.Close() 192 + 193 + require.NoError(t, err) 194 + 195 + err = pull_service.Merge(ctx, pull, user, gitRepo, repo_model.MergeStyleMerge, pull.HeadCommitID, "merge", false) 196 + require.NoError(t, err) 197 + 198 + assert.False(t, pull.Issue.IsClosed) 199 + assert.True(t, pull.CanAutoMerge()) 200 + assert.False(t, pull.IsWorkInProgress(ctx)) 201 + 202 + return pull 203 + } 204 + 205 + func createPullRequest(t *testing.T, user *user_model.User, repo *repo_model.Repository, name string) *issues_model.PullRequest { 206 + branch := "branch-" + name 207 + title := "Testing " + name 208 + 209 + _, err := files_service.ChangeRepoFiles(git.DefaultContext, repo, user, &files_service.ChangeRepoFilesOptions{ 210 + Files: []*files_service.ChangeRepoFile{ 211 + { 212 + Operation: "update", 213 + TreePath: "README.md", 214 + ContentReader: strings.NewReader("Update README"), 215 + }, 216 + }, 217 + Message: "Update README", 218 + OldBranch: "main", 219 + NewBranch: branch, 220 + Author: &files_service.IdentityOptions{ 221 + Name: user.Name, 222 + Email: user.Email, 223 + }, 224 + Committer: &files_service.IdentityOptions{ 225 + Name: user.Name, 226 + Email: user.Email, 227 + }, 228 + Dates: &files_service.CommitDateOptions{ 229 + Author: time.Now(), 230 + Committer: time.Now(), 231 + }, 232 + }) 233 + 234 + require.NoError(t, err) 235 + 236 + pullIssue := &issues_model.Issue{ 237 + RepoID: repo.ID, 238 + Title: title, 239 + PosterID: user.ID, 240 + Poster: user, 241 + IsPull: true, 242 + } 243 + 244 + pullRequest := &issues_model.PullRequest{ 245 + HeadRepoID: repo.ID, 246 + BaseRepoID: repo.ID, 247 + HeadBranch: branch, 248 + BaseBranch: "main", 249 + HeadRepo: repo, 250 + BaseRepo: repo, 251 + Type: issues_model.PullRequestGitea, 252 + } 253 + err = pull_service.NewPullRequest(git.DefaultContext, repo, pullIssue, nil, nil, pullRequest, nil) 254 + require.NoError(t, err) 255 + 256 + return pullRequest 257 + }
+149 -25
web_src/js/components/ContextPopup.test.js
··· 1 - import {mount, flushPromises} from '@vue/test-utils'; 1 + import {flushPromises, mount} from '@vue/test-utils'; 2 2 import ContextPopup from './ContextPopup.vue'; 3 3 4 - test('renders a issue info popup', async () => { 5 - const owner = 'user2'; 6 - const repo = 'repo1'; 7 - const index = 1; 4 + async function assertPopup(popupData, expectedIconColor, expectedIcon) { 5 + const date = new Date('2024-07-13T22:00:00Z'); 6 + 8 7 vi.spyOn(global, 'fetch').mockResolvedValue({ 9 8 json: vi.fn().mockResolvedValue({ 10 9 ok: true, 11 - created_at: '2023-09-30T19:00:00Z', 12 - repository: {full_name: owner}, 10 + created_at: date.toISOString(), 11 + repository: {full_name: 'user2/repo1'}, 12 + ...popupData, 13 + }), 14 + ok: true, 15 + }); 16 + 17 + const popup = mount(ContextPopup); 18 + popup.vm.$el.dispatchEvent(new CustomEvent('ce-load-context-popup', { 19 + detail: {owner: 'user2', repo: 'repo1', index: popupData.number}, 20 + })); 21 + await flushPromises(); 22 + 23 + expect(popup.get('p:nth-of-type(1)').text()).toEqual(`user2/repo1 on ${date.toLocaleDateString(undefined, {year: 'numeric', month: 'short', day: 'numeric'})}`); 24 + expect(popup.get('p:nth-of-type(2)').text()).toEqual(`${popupData.title} #${popupData.number}`); 25 + expect(popup.get('p:nth-of-type(3)').text()).toEqual(popupData.body); 26 + 27 + expect(popup.get('svg').classes()).toContain(expectedIcon); 28 + expect(popup.get('svg').classes()).toContain(expectedIconColor); 29 + 30 + for (const l of popupData.labels) { 31 + expect(popup.findAll('.ui.label').map((x) => x.text())).toContain(l.name); 32 + } 33 + } 34 + 35 + test('renders an open issue popup', async () => { 36 + await assertPopup({ 37 + title: 'Open Issue', 38 + body: 'Open Issue Body', 39 + number: 1, 40 + labels: [{color: 'd21b1fff', name: 'Bug'}, {color: 'aaff00', name: 'Confirmed'}], 41 + state: 'open', 42 + pull_request: null, 43 + }, 'green', 'octicon-issue-opened'); 44 + }); 45 + 46 + test('renders a closed issue popup', async () => { 47 + await assertPopup({ 48 + title: 'Closed Issue', 49 + body: 'Closed Issue Body', 50 + number: 1, 51 + labels: [{color: 'd21b1fff', name: 'Bug'}, {color: 'aaff00', name: 'Confirmed'}], 52 + state: 'closed', 53 + pull_request: null, 54 + }, 'red', 'octicon-issue-closed'); 55 + }); 56 + 57 + test('renders an open PR popup', async () => { 58 + await assertPopup({ 59 + title: 'Open PR', 60 + body: 'Open PR Body', 61 + number: 1, 62 + labels: [{color: 'd21b1fff', name: 'Bug'}, {color: 'aaff00', name: 'Confirmed'}], 63 + state: 'open', 64 + pull_request: {merged: false, draft: false}, 65 + }, 'green', 'octicon-git-pull-request'); 66 + }); 67 + 68 + test('renders an open WIP PR popup', async () => { 69 + await assertPopup({ 70 + title: 'WIP: Open PR', 71 + body: 'WIP Open PR Body', 72 + number: 1, 73 + labels: [{color: 'd21b1fff', name: 'Bug'}, {color: 'aaff00', name: 'Confirmed'}], 74 + state: 'open', 75 + pull_request: {merged: false, draft: true}, 76 + }, 'grey', 'octicon-git-pull-request-draft'); 77 + }); 78 + 79 + test('renders a closed PR popup', async () => { 80 + await assertPopup({ 81 + title: 'Closed PR', 82 + body: 'Closed PR Body', 83 + number: 1, 84 + labels: [{color: 'd21b1fff', name: 'Bug'}, {color: 'aaff00', name: 'Confirmed'}], 85 + state: 'closed', 86 + pull_request: {merged: false, draft: false}, 87 + }, 'red', 'octicon-git-pull-request-closed'); 88 + }); 89 + 90 + test('renders a closed WIP PR popup', async () => { 91 + await assertPopup({ 92 + title: 'WIP: Closed PR', 93 + body: 'WIP Closed PR Body', 94 + number: 1, 95 + labels: [{color: 'd21b1fff', name: 'Bug'}, {color: 'aaff00', name: 'Confirmed'}], 96 + state: 'closed', 97 + pull_request: {merged: false, draft: true}, 98 + }, 'red', 'octicon-git-pull-request-closed'); 99 + }); 100 + 101 + test('renders a merged PR popup', async () => { 102 + await assertPopup({ 103 + title: 'Merged PR', 104 + body: 'Merged PR Body', 105 + number: 1, 106 + labels: [{color: 'd21b1fff', name: 'Bug'}, {color: 'aaff00', name: 'Confirmed'}], 107 + state: 'closed', 108 + pull_request: {merged: true, draft: false}, 109 + }, 'purple', 'octicon-git-merge'); 110 + }); 111 + 112 + test('renders an issue popup with escaped HTML', async () => { 113 + const evil = '<script class="evil">alert("evil message");</script>'; 114 + 115 + vi.spyOn(global, 'fetch').mockResolvedValue({ 116 + json: vi.fn().mockResolvedValue({ 117 + ok: true, 118 + created_at: '2024-07-13T22:00:00Z', 119 + repository: {full_name: evil}, 120 + title: evil, 121 + body: evil, 122 + labels: [{color: '000666', name: evil}], 123 + state: 'open', 13 124 pull_request: null, 125 + }), 126 + ok: true, 127 + }); 128 + 129 + const popup = mount(ContextPopup); 130 + popup.vm.$el.dispatchEvent(new CustomEvent('ce-load-context-popup', { 131 + detail: {owner: evil, repo: evil, index: 1}, 132 + })); 133 + await flushPromises(); 134 + 135 + expect(() => popup.get('.evil')).toThrowError(); 136 + expect(popup.get('p:nth-of-type(1)').text()).toContain(evil); 137 + expect(popup.get('p:nth-of-type(2)').text()).toContain(evil); 138 + expect(popup.get('p:nth-of-type(3)').text()).toContain(evil); 139 + }); 140 + 141 + test('renders an issue popup with emojis', async () => { 142 + vi.spyOn(global, 'fetch').mockResolvedValue({ 143 + json: vi.fn().mockResolvedValue({ 144 + ok: true, 145 + created_at: '2024-07-13T22:00:00Z', 146 + repository: {full_name: 'user2/repo1'}, 147 + title: 'Title', 148 + body: 'Body', 149 + labels: [{color: '000666', name: 'Tag :+1:'}], 14 150 state: 'open', 15 - title: 'Normal issue', 16 - body: 'Lorem ipsum...', 17 - number: index, 18 - labels: [{color: 'ee0701', name: "Bug :+1: <script class='evil'>alert('Oh no!');</script>"}], 151 + pull_request: null, 19 152 }), 20 153 ok: true, 21 154 }); 22 155 23 - const wrapper = mount(ContextPopup); 24 - wrapper.vm.$el.dispatchEvent(new CustomEvent('ce-load-context-popup', {detail: {owner, repo, index}})); 156 + const popup = mount(ContextPopup); 157 + popup.vm.$el.dispatchEvent(new CustomEvent('ce-load-context-popup', { 158 + detail: {owner: 'user2', repo: 'repo1', index: 1}, 159 + })); 25 160 await flushPromises(); 26 161 27 - // Header 28 - expect(wrapper.get('p:nth-of-type(1)').text()).toEqual('user2 on Sep 30, 2023'); 29 - // Title 30 - expect(wrapper.get('p:nth-of-type(2)').text()).toEqual('Normal issue #1'); 31 - // Body 32 - expect(wrapper.get('p:nth-of-type(3)').text()).toEqual('Lorem ipsum...'); 33 - // Check that the state is correct. 34 - expect(wrapper.get('svg').classes()).toContain('octicon-issue-opened'); 35 - // Ensure that script is not an element. 36 - expect(() => wrapper.get('.evil')).toThrowError(); 37 - // Check content of label 38 - expect(wrapper.get('.ui.label').text()).toContain("Bug 👍 <script class='evil'>alert('Oh no!');</script>"); 162 + expect(popup.get('.ui.label').text()).toEqual('Tag 👍'); 39 163 });