From d417a30802fcd7b5d058ada919dc0704e0a2c1cf Mon Sep 17 00:00:00 2001 From: kolaente Date: Wed, 20 May 2026 17:19:40 +0200 Subject: [PATCH] test(e2e): cover comment replies end-to-end Drives the reply flow through the browser: existing comment is quoted via the Reply action, the prefilled blockquote round-trips to the saved reply, the chevron jumps back to the original and applies the brief highlight. --- frontend/tests/e2e/task/comment-reply.spec.ts | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 frontend/tests/e2e/task/comment-reply.spec.ts diff --git a/frontend/tests/e2e/task/comment-reply.spec.ts b/frontend/tests/e2e/task/comment-reply.spec.ts new file mode 100644 index 000000000..231b2c3f7 --- /dev/null +++ b/frontend/tests/e2e/task/comment-reply.spec.ts @@ -0,0 +1,56 @@ +import {test, expect} from '../../support/fixtures' +import {ProjectFactory} from '../../factories/project' +import {TaskFactory} from '../../factories/task' +import {TaskCommentFactory} from '../../factories/task_comment' +import {createDefaultViews} from '../project/prepareProjects' + +test.describe('Reply to a task comment', () => { + test.beforeEach(async ({authenticatedPage: page, currentUser}) => { + await ProjectFactory.create(1, {owner_id: currentUser.id}) + await createDefaultViews(1) + await TaskFactory.create(1, {id: 1, created_by_id: currentUser.id}) + }) + + test('Reply action prefills the editor with a quoted blockquote and the saved reply renders an author header + chevron that jumps to the original', async ({authenticatedPage: page, currentUser}) => { + await TaskCommentFactory.create(1, { + id: 1, + task_id: 1, + author_id: currentUser.id, + comment: 'Original message that we will quote.', + }) + + await page.goto('/tasks/1') + await page.waitForLoadState('networkidle') + + const originalComment = page.locator('#comment-1') + await expect(originalComment).toBeVisible({timeout: 10000}) + + // The Reply action lives in the per-comment bottom-actions list. + await originalComment.getByRole('button', {name: 'Reply', exact: true}).click() + + // The new-comment editor (the contenteditable one) should now contain + // the prefilled blockquote pointing back at comment 1. + const newCommentEditor = page.locator('.task-view .comments .media.comment .tiptap__editor .tiptap.ProseMirror[contenteditable="true"]').last() + await expect(newCommentEditor).toBeVisible() + await expect(newCommentEditor.locator('blockquote[data-comment-id="1"]')).toBeVisible() + await expect(newCommentEditor.locator('blockquote[data-comment-id="1"]')).toContainText('Original message that we will quote.') + + // Append a reply body after the auto-inserted paragraph. + await newCommentEditor.click() + await page.keyboard.press('End') + await page.keyboard.type('Thanks for that!') + + await page.getByRole('button', {name: 'Comment', exact: true}).click() + + // The newly-rendered reply should carry the quote header + chevron. + const reply = page.locator('.task-view .comments .media.comment').nth(1) + await expect(reply).toBeVisible() + const quote = reply.locator('blockquote.comment-quote[data-comment-id="1"]') + await expect(quote).toBeVisible() + await expect(quote.locator('.comment-quote__jump')).toBeVisible() + + // Clicking the chevron scrolls to and briefly highlights the original. + await quote.locator('.comment-quote__jump').click() + await expect(originalComment).toHaveClass(/comment-highlight/, {timeout: 2000}) + }) +})