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 'fix(ui): allow unreacting from comment popover' (#4798) from solomonv/forgejo:issue-reaction-fixes into forgejo

Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/4798
Reviewed-by: Gusted <gusted@noreply.codeberg.org>

+80 -27
+14 -26
routers/web/repo/issue.go
··· 3363 3363 log.Info("CreateIssueReaction: %s", err) 3364 3364 break 3365 3365 } 3366 - // Reload new reactions 3367 - issue.Reactions = nil 3368 - if err = issue.LoadAttributes(ctx); err != nil { 3369 - log.Info("issue.LoadAttributes: %s", err) 3370 - break 3371 - } 3372 3366 3373 3367 log.Trace("Reaction for issue created: %d/%d/%d", ctx.Repo.Repository.ID, issue.ID, reaction.ID) 3374 3368 case "unreact": ··· 3377 3371 return 3378 3372 } 3379 3373 3380 - // Reload new reactions 3381 - issue.Reactions = nil 3382 - if err := issue.LoadAttributes(ctx); err != nil { 3383 - log.Info("issue.LoadAttributes: %s", err) 3384 - break 3385 - } 3386 - 3387 3374 log.Trace("Reaction for issue removed: %d/%d", ctx.Repo.Repository.ID, issue.ID) 3388 3375 default: 3389 3376 ctx.NotFound(fmt.Sprintf("Unknown action %s", ctx.Params(":action")), nil) 3377 + return 3378 + } 3379 + 3380 + // Reload new reactions 3381 + issue.Reactions = nil 3382 + if err := issue.LoadAttributes(ctx); err != nil { 3383 + ctx.ServerError("ChangeIssueReaction.LoadAttributes", err) 3390 3384 return 3391 3385 } 3392 3386 ··· 3470 3464 log.Info("CreateCommentReaction: %s", err) 3471 3465 break 3472 3466 } 3473 - // Reload new reactions 3474 - comment.Reactions = nil 3475 - if err = comment.LoadReactions(ctx, ctx.Repo.Repository); err != nil { 3476 - log.Info("comment.LoadReactions: %s", err) 3477 - break 3478 - } 3479 3467 3480 3468 log.Trace("Reaction for comment created: %d/%d/%d/%d", ctx.Repo.Repository.ID, comment.Issue.ID, comment.ID, reaction.ID) 3481 3469 case "unreact": ··· 3484 3472 return 3485 3473 } 3486 3474 3487 - // Reload new reactions 3488 - comment.Reactions = nil 3489 - if err = comment.LoadReactions(ctx, ctx.Repo.Repository); err != nil { 3490 - log.Info("comment.LoadReactions: %s", err) 3491 - break 3492 - } 3493 - 3494 3475 log.Trace("Reaction for comment removed: %d/%d/%d", ctx.Repo.Repository.ID, comment.Issue.ID, comment.ID) 3495 3476 default: 3496 3477 ctx.NotFound(fmt.Sprintf("Unknown action %s", ctx.Params(":action")), nil) 3478 + return 3479 + } 3480 + 3481 + // Reload new reactions 3482 + comment.Reactions = nil 3483 + if err = comment.LoadReactions(ctx, ctx.Repo.Repository); err != nil { 3484 + ctx.ServerError("ChangeCommentReaction.LoadReactions", err) 3497 3485 return 3498 3486 } 3499 3487
+65
tests/e2e/reaction-selectors.test.e2e.js
··· 1 + // @ts-check 2 + import {test, expect} from '@playwright/test'; 3 + import {login_user, load_logged_in_context} from './utils_e2e.js'; 4 + 5 + test.beforeAll(async ({browser}, workerInfo) => { 6 + await login_user(browser, workerInfo, 'user2'); 7 + }); 8 + 9 + const assertReactionCounts = (comment, counts) => 10 + expect(async () => { 11 + await expect(comment.locator('.reactions')).toBeVisible(); 12 + 13 + const reactions = Object.fromEntries( 14 + await Promise.all( 15 + ( 16 + await comment 17 + .locator(`.reactions [role=button][data-reaction-content]`) 18 + .all() 19 + ).map(async (button) => [ 20 + await button.getAttribute('data-reaction-content'), 21 + parseInt(await button.locator('.reaction-count').textContent()), 22 + ]), 23 + ), 24 + ); 25 + return expect(reactions).toStrictEqual(counts); 26 + }).toPass(); 27 + 28 + async function toggleReaction(menu, reaction) { 29 + await menu.evaluateAll((menus) => menus[0].focus()); 30 + await menu.locator('.add-reaction').click(); 31 + await menu.locator(`[role=menuitem][data-reaction-content="${reaction}"]`).click(); 32 + } 33 + 34 + test('Reaction Selectors', async ({browser}, workerInfo) => { 35 + const context = await load_logged_in_context(browser, workerInfo, 'user2'); 36 + const page = await context.newPage(); 37 + 38 + const response = await page.goto('/user2/repo1/issues/1'); 39 + await expect(response?.status()).toBe(200); 40 + 41 + const comment = page.locator('.comment#issuecomment-2').first(); 42 + 43 + const topPicker = comment.locator('.actions [role=menu].select-reaction'); 44 + const bottomPicker = comment.locator('.reactions').getByRole('menu'); 45 + 46 + await assertReactionCounts(comment, {'laugh': 2}); 47 + 48 + await toggleReaction(topPicker, '+1'); 49 + await assertReactionCounts(comment, {'laugh': 2, '+1': 1}); 50 + 51 + await toggleReaction(bottomPicker, '+1'); 52 + await assertReactionCounts(comment, {'laugh': 2}); 53 + 54 + await toggleReaction(bottomPicker, '-1'); 55 + await assertReactionCounts(comment, {'laugh': 2, '-1': 1}); 56 + 57 + await toggleReaction(topPicker, '-1'); 58 + await assertReactionCounts(comment, {'laugh': 2}); 59 + 60 + await comment.locator('.reactions [role=button][data-reaction-content=laugh]').click(); 61 + await assertReactionCounts(comment, {'laugh': 1}); 62 + 63 + await toggleReaction(topPicker, 'laugh'); 64 + await assertReactionCounts(comment, {'laugh': 2}); 65 + });
+1 -1
web_src/js/features/comp/ReactionSelector.js
··· 9 9 10 10 const actionUrl = this.closest('[data-action-url]')?.getAttribute('data-action-url'); 11 11 const reactionContent = this.getAttribute('data-reaction-content'); 12 - const hasReacted = this.closest('.ui.segment.reactions')?.querySelector(`a[data-reaction-content="${reactionContent}"]`)?.getAttribute('data-has-reacted') === 'true'; 12 + const hasReacted = this.closest('.comment')?.querySelector(`.ui.segment.reactions a[data-reaction-content="${reactionContent}"]`)?.getAttribute('data-has-reacted') === 'true'; 13 13 14 14 const res = await POST(`${actionUrl}/${hasReacted ? 'unreact' : 'react'}`, { 15 15 data: new URLSearchParams({content: reactionContent}),