0
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2024-12-30 22:34:01 -05:00

Fixed incorrect replies pagination count after deleting reply

closes https://linear.app/ghost/issue/PLG-303

- when deleting a reply our "replies left" calculation was getting out of sync because the `count.replies` state on the parent comment wasn't being updated, the result was for each comment deleted we were displaying 1 more reply that was still to load
- updated the `deleteComment` action to also modify the parent comment's `count.replies` value when a reply was deleted ensuring our "replies left" calculation remains correct
This commit is contained in:
Kevin Ansfield 2024-12-12 15:41:03 +00:00
parent bd9f6bb216
commit 46f6f49c03
3 changed files with 53 additions and 36 deletions

View file

@ -280,13 +280,13 @@ async function deleteComment({state, api, data: comment}: {state: EditableAppCon
});
return {
comments: state.comments.map((c) => {
comments: state.comments.map((topLevelComment) => {
// If the comment has replies we want to keep it so the replies are
// still visible, but mark the comment as deleted. Otherwise remove it.
if (c.id === comment.id) {
if (c.replies.length > 0) {
if (topLevelComment.id === comment.id) {
if (topLevelComment.replies.length > 0) {
return {
...c,
...topLevelComment,
status: 'deleted'
};
} else {
@ -294,11 +294,22 @@ async function deleteComment({state, api, data: comment}: {state: EditableAppCon
}
}
const updatedReplies = c.replies.filter(r => r.id !== comment.id);
return {
...c,
const originalLength = topLevelComment.replies.length;
const updatedReplies = topLevelComment.replies.filter(reply => reply.id !== comment.id);
const hasDeletedReply = originalLength !== updatedReplies.length;
const updatedTopLevelComment = {
...topLevelComment,
replies: updatedReplies
};
// When a reply is deleted we need to update the parent's count so
// pagination displays the correct number of replies still to load
if (hasDeletedReply && topLevelComment.count?.replies) {
topLevelComment.count.replies = topLevelComment.count.replies - 1;
}
return updatedTopLevelComment;
}).filter(Boolean),
commentCount: state.commentCount - 1
};

View file

@ -12,7 +12,7 @@ const RepliesPagination: React.FC<Props> = ({loadMore, count}) => {
const shortText = t('{{amount}} more', {amount: formatNumber(count)});
return (
<div className="flex w-full items-center justify-start">
<div className="flex w-full items-center justify-start" data-testid="replies-pagination">
<button className="text-md group mb-10 ml-[48px] flex w-auto items-center px-0 pb-2 pt-0 text-left font-sans font-semibold text-neutral-700 sm:mb-12 dark:text-white " data-testid="reply-pagination-button" type="button" onClick={loadMore}>
<span className="flex h-[40px] w-auto items-center justify-center whitespace-nowrap rounded-[6px] bg-black/5 px-4 py-2 text-center font-sans text-sm font-semibold text-neutral-700 outline-0 transition-all duration-150 hover:bg-black/10 dark:bg-white/15 dark:text-neutral-300 dark:hover:bg-white/20 dark:hover:text-neutral-100"> <span className="ml-1 hidden sm:inline">{longText}</span><span className="ml-1 inline sm:hidden">{shortText}</span> </span>
</button>

View file

@ -341,6 +341,13 @@ test.describe('Actions', async () => {
);
});
async function deleteComment(page, frame, commentComponent) {
await commentComponent.getByTestId('more-button').first().click();
await frame.getByTestId('delete').click();
const popupIframe = page.frameLocator('iframe[title="deletePopup"]');
await popupIframe.getByTestId('delete-popup-confirm').click();
}
test('Can delete a comment', async ({page}) => {
const loggedInMember = buildMember();
mockedApi.setMember(loggedInMember);
@ -352,15 +359,8 @@ test.describe('Actions', async () => {
const {frame} = await initializeTest(page);
const comment = frame.getByTestId('comment-component').nth(0);
const moreButton = comment.getByTestId('more-button').first();
await moreButton.click();
await frame.getByTestId('delete').click();
const popupIframe = page.frameLocator('iframe[title="deletePopup"]');
await expect(popupIframe.getByTestId('delete-popup')).toBeVisible();
await popupIframe.getByTestId('delete-popup-confirm').click();
const commentToDelete = frame.getByTestId('comment-component').nth(0);
await deleteComment(page, frame, commentToDelete);
await expect(frame.getByTestId('comment-component')).toHaveCount(0);
});
@ -382,20 +382,33 @@ test.describe('Actions', async () => {
const {frame} = await initializeTest(page);
const comment = frame.getByTestId('comment-component').nth(0);
const reply = comment.getByTestId('comment-component').nth(0);
const moreButton = reply.getByTestId('more-button').first();
await moreButton.click();
await frame.getByTestId('delete').click();
const popupIframe = page.frameLocator('iframe[title="deletePopup"]');
await expect(popupIframe.getByTestId('delete-popup')).toBeVisible();
await popupIframe.getByTestId('delete-popup-confirm').click();
const replyToDelete = comment.getByTestId('comment-component').nth(0);
await deleteComment(page, frame, replyToDelete);
await expect(frame.getByTestId('comment-component')).toHaveCount(1);
await expect(frame.getByTestId('replies-line')).not.toBeVisible();
});
test('Deleting a reply updates pagination', async ({page}) => {
const loggedInMember = buildMember();
mockedApi.setMember(loggedInMember);
mockedApi.addComment({
html: '<p>Parent comment</p>',
// 6 replies
replies: Array.from({length: 6}, (_, i) => buildReply({member: loggedInMember, html: `<p>Reply ${i + 1}</p>`}))
});
const {frame} = await initializeTest(page);
await expect(frame.getByTestId('replies-pagination')).toContainText('3');
const replyToDelete = frame.getByTestId('comment-component').nth(2);
await deleteComment(page, frame, replyToDelete);
// Replies count does not change - we still have 3 unloaded replies
await expect(frame.getByTestId('replies-pagination')).toContainText('3');
});
test('Can delete a comment with replies', async ({page}) => {
const loggedInMember = buildMember();
mockedApi.setMember(loggedInMember);
@ -412,22 +425,15 @@ test.describe('Actions', async () => {
const {frame} = await initializeTest(page);
const comment = frame.getByTestId('comment-component').nth(0);
const moreButton = comment.getByTestId('more-button').first();
await moreButton.click();
await frame.getByTestId('delete').click();
const popupIframe = page.frameLocator('iframe[title="deletePopup"]');
await expect(popupIframe.getByTestId('delete-popup')).toBeVisible();
await popupIframe.getByTestId('delete-popup-confirm').click();
const commentToDelete = frame.getByTestId('comment-component').nth(0);
await deleteComment(page, frame, commentToDelete);
await expect(frame.getByTestId('comment-component')).toHaveCount(2);
await expect(frame.getByText('This comment has been removed')).toBeVisible();
await expect(frame.getByTestId('replies-line')).toBeVisible();
});
test.describe('Sorting - flag needs to be enabled', () => {
test.describe('Sorting', () => {
test('Renders Sorting Form dropdown', async ({page}) => {
mockedApi.addComment({
html: '<p>This is comment 1</p>'