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:
parent
bd9f6bb216
commit
46f6f49c03
3 changed files with 53 additions and 36 deletions
|
@ -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
|
||||
};
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>'
|
||||
|
|
Loading…
Reference in a new issue