✨(frontend) add comments feature
Implemented the comments feature for the document editor. We are now able to add, view, and manage comments within the document editor interface.
This commit is contained in:
289
src/frontend/apps/e2e/__tests__/app-impress/doc-comments.spec.ts
Normal file
289
src/frontend/apps/e2e/__tests__/app-impress/doc-comments.spec.ts
Normal file
@@ -0,0 +1,289 @@
|
||||
import { expect, test } from '@playwright/test';
|
||||
|
||||
import { createDoc, getOtherBrowserName, verifyDocName } from './utils-common';
|
||||
import { writeInEditor } from './utils-editor';
|
||||
import {
|
||||
addNewMember,
|
||||
connectOtherUserToDoc,
|
||||
updateRoleUser,
|
||||
updateShareLink,
|
||||
} from './utils-share';
|
||||
|
||||
test.beforeEach(async ({ page }) => {
|
||||
await page.goto('/');
|
||||
});
|
||||
|
||||
test.describe('Doc Comments', () => {
|
||||
test('it checks comments with 2 users in real time', async ({
|
||||
page,
|
||||
browserName,
|
||||
}) => {
|
||||
const [docTitle] = await createDoc(page, 'comment-doc', browserName, 1);
|
||||
|
||||
// We share the doc with another user
|
||||
const otherBrowserName = getOtherBrowserName(browserName);
|
||||
await page.getByRole('button', { name: 'Share' }).click();
|
||||
await addNewMember(page, 0, 'Administrator', otherBrowserName);
|
||||
|
||||
await expect(
|
||||
page
|
||||
.getByRole('listbox', { name: 'Suggestions' })
|
||||
.getByText(new RegExp(otherBrowserName)),
|
||||
).toBeVisible();
|
||||
|
||||
await page.getByRole('button', { name: 'close' }).click();
|
||||
|
||||
// We add a comment with the first user
|
||||
const editor = await writeInEditor({ page, text: 'Hello World' });
|
||||
await editor.getByText('Hello').selectText();
|
||||
await page.getByRole('button', { name: 'Add comment' }).click();
|
||||
|
||||
const thread = page.locator('.bn-thread');
|
||||
await thread.getByRole('paragraph').first().fill('This is a comment');
|
||||
await thread.locator('[data-test="save"]').click();
|
||||
await expect(thread.getByText('This is a comment').first()).toBeHidden();
|
||||
|
||||
await editor.getByText('Hello').click();
|
||||
|
||||
await thread.getByText('This is a comment').first().hover();
|
||||
|
||||
// We add a reaction with the first user
|
||||
await thread.locator('[data-test="addreaction"]').first().click();
|
||||
await page.getByRole('button', { name: '👍' }).click();
|
||||
|
||||
await expect(thread.getByText('This is a comment').first()).toBeVisible();
|
||||
await expect(thread.getByText(`E2E ${browserName}`).first()).toBeVisible();
|
||||
await expect(thread.locator('.bn-comment-reaction')).toHaveText('👍1');
|
||||
|
||||
const urlCommentDoc = page.url();
|
||||
|
||||
const { otherPage, cleanup } = await connectOtherUserToDoc({
|
||||
otherBrowserName,
|
||||
docUrl: urlCommentDoc,
|
||||
docTitle,
|
||||
});
|
||||
|
||||
const otherEditor = otherPage.locator('.ProseMirror');
|
||||
await otherEditor.getByText('Hello').click();
|
||||
const otherThread = otherPage.locator('.bn-thread');
|
||||
|
||||
await otherThread.getByText('This is a comment').first().hover();
|
||||
await otherThread.locator('[data-test="addreaction"]').first().click();
|
||||
await otherPage.getByRole('button', { name: '👍' }).click();
|
||||
|
||||
// We check that the comment made by the first user is visible for the second user
|
||||
await expect(
|
||||
otherThread.getByText('This is a comment').first(),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
otherThread.getByText(`E2E ${browserName}`).first(),
|
||||
).toBeVisible();
|
||||
await expect(otherThread.locator('.bn-comment-reaction')).toHaveText('👍2');
|
||||
|
||||
// We add a comment with the second user
|
||||
await otherThread
|
||||
.getByRole('paragraph')
|
||||
.last()
|
||||
.fill('This is a comment from the other user');
|
||||
await otherThread.locator('[data-test="save"]').click();
|
||||
|
||||
// We check that the second user can see the comment he just made
|
||||
await expect(
|
||||
otherThread.getByText('This is a comment from the other user').first(),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
otherThread.getByText(`E2E ${otherBrowserName}`).first(),
|
||||
).toBeVisible();
|
||||
|
||||
// We check that the first user can see the comment made by the second user in real time
|
||||
await expect(
|
||||
thread.getByText('This is a comment from the other user').first(),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
thread.getByText(`E2E ${otherBrowserName}`).first(),
|
||||
).toBeVisible();
|
||||
|
||||
await cleanup();
|
||||
});
|
||||
|
||||
test('it checks the comments interactions', async ({ page, browserName }) => {
|
||||
await createDoc(page, 'comment-interaction', browserName, 1);
|
||||
|
||||
// Checks add react reaction
|
||||
const editor = page.locator('.ProseMirror');
|
||||
await editor.locator('.bn-block-outer').last().fill('Hello World');
|
||||
await editor.getByText('Hello').selectText();
|
||||
await page.getByRole('button', { name: 'Add comment' }).click();
|
||||
|
||||
const thread = page.locator('.bn-thread');
|
||||
await thread.getByRole('paragraph').first().fill('This is a comment');
|
||||
await thread.locator('[data-test="save"]').click();
|
||||
await expect(thread.getByText('This is a comment').first()).toBeHidden();
|
||||
|
||||
// Check background color changed
|
||||
await expect(editor.getByText('Hello')).toHaveCSS(
|
||||
'background-color',
|
||||
'rgba(237, 180, 0, 0.4)',
|
||||
);
|
||||
await editor.getByText('Hello').click();
|
||||
|
||||
await thread.getByText('This is a comment').first().hover();
|
||||
|
||||
// We add a reaction with the first user
|
||||
await thread.locator('[data-test="addreaction"]').first().click();
|
||||
await page.getByRole('button', { name: '👍' }).click();
|
||||
|
||||
await expect(thread.locator('.bn-comment-reaction')).toHaveText('👍1');
|
||||
|
||||
// Edit Comment
|
||||
await thread.getByText('This is a comment').first().hover();
|
||||
await thread.locator('[data-test="moreactions"]').first().click();
|
||||
await thread.getByRole('menuitem', { name: 'Edit comment' }).click();
|
||||
const commentEditor = thread.getByText('This is a comment').first();
|
||||
await commentEditor.fill('This is an edited comment');
|
||||
const saveBtn = thread.getByRole('button', { name: 'Save' });
|
||||
await saveBtn.click();
|
||||
await expect(saveBtn).toBeHidden();
|
||||
await expect(
|
||||
thread.getByText('This is an edited comment').first(),
|
||||
).toBeVisible();
|
||||
await expect(thread.getByText('This is a comment').first()).toBeHidden();
|
||||
|
||||
// Add second comment
|
||||
await thread.getByRole('paragraph').last().fill('This is a second comment');
|
||||
await thread.getByRole('button', { name: 'Save' }).click();
|
||||
await expect(
|
||||
thread.getByText('This is an edited comment').first(),
|
||||
).toBeVisible();
|
||||
await expect(
|
||||
thread.getByText('This is a second comment').first(),
|
||||
).toBeVisible();
|
||||
|
||||
// Delete second comment
|
||||
await thread.getByText('This is a second comment').first().hover();
|
||||
await thread.locator('[data-test="moreactions"]').first().click();
|
||||
await thread.getByRole('menuitem', { name: 'Delete comment' }).click();
|
||||
await expect(
|
||||
thread.getByText('This is a second comment').first(),
|
||||
).toBeHidden();
|
||||
|
||||
// Resolve thread
|
||||
await thread.getByText('This is an edited comment').first().hover();
|
||||
await thread.locator('[data-test="resolve"]').click();
|
||||
await expect(thread).toBeHidden();
|
||||
await expect(editor.getByText('Hello')).toHaveCSS(
|
||||
'background-color',
|
||||
'rgba(0, 0, 0, 0)',
|
||||
);
|
||||
});
|
||||
|
||||
test('it checks the comments abilities', async ({ page, browserName }) => {
|
||||
test.slow();
|
||||
|
||||
const [docTitle] = await createDoc(page, 'comment-doc', browserName, 1);
|
||||
|
||||
// We share the doc with another user
|
||||
const otherBrowserName = getOtherBrowserName(browserName);
|
||||
|
||||
// Add a new member with editor role
|
||||
await page.getByRole('button', { name: 'Share' }).click();
|
||||
await addNewMember(page, 0, 'Editor', otherBrowserName);
|
||||
|
||||
await expect(
|
||||
page
|
||||
.getByRole('listbox', { name: 'Suggestions' })
|
||||
.getByText(new RegExp(otherBrowserName)),
|
||||
).toBeVisible();
|
||||
|
||||
const urlCommentDoc = page.url();
|
||||
|
||||
const { otherPage, cleanup } = await connectOtherUserToDoc({
|
||||
otherBrowserName,
|
||||
docUrl: urlCommentDoc,
|
||||
docTitle,
|
||||
});
|
||||
|
||||
const otherEditor = await writeInEditor({
|
||||
page: otherPage,
|
||||
text: 'Hello, I can edit the document',
|
||||
});
|
||||
await expect(
|
||||
otherEditor.getByText('Hello, I can edit the document'),
|
||||
).toBeVisible();
|
||||
await otherEditor.getByText('Hello').selectText();
|
||||
await otherPage.getByRole('button', { name: 'Comment' }).click();
|
||||
const otherThread = otherPage.locator('.bn-thread');
|
||||
await otherThread
|
||||
.getByRole('paragraph')
|
||||
.first()
|
||||
.fill('I can add a comment');
|
||||
await otherThread.locator('[data-test="save"]').click();
|
||||
await expect(
|
||||
otherThread.getByText('I can add a comment').first(),
|
||||
).toBeHidden();
|
||||
|
||||
await expect(otherEditor.getByText('Hello')).toHaveCSS(
|
||||
'background-color',
|
||||
'rgba(237, 180, 0, 0.4)',
|
||||
);
|
||||
|
||||
// We change the role of the second user to reader
|
||||
await updateRoleUser(page, 'Reader', `user.test@${otherBrowserName}.test`);
|
||||
|
||||
// With the reader role, the second user cannot see comments
|
||||
await otherPage.reload();
|
||||
await verifyDocName(otherPage, docTitle);
|
||||
|
||||
await expect(otherEditor.getByText('Hello')).toHaveCSS(
|
||||
'background-color',
|
||||
'rgba(0, 0, 0, 0)',
|
||||
);
|
||||
await otherEditor.getByText('Hello').click();
|
||||
await expect(otherThread).toBeHidden();
|
||||
await otherEditor.getByText('Hello').selectText();
|
||||
await expect(
|
||||
otherPage.getByRole('button', { name: 'Comment' }),
|
||||
).toBeHidden();
|
||||
|
||||
await otherPage.reload();
|
||||
|
||||
// Change the link role of the doc to set it in commenting mode
|
||||
await updateShareLink(page, 'Public', 'Editing');
|
||||
|
||||
// Anonymous user can see and add comments
|
||||
await otherPage.getByRole('button', { name: 'Logout' }).click();
|
||||
|
||||
await otherPage.goto(urlCommentDoc);
|
||||
|
||||
await verifyDocName(otherPage, docTitle);
|
||||
|
||||
await expect(otherEditor.getByText('Hello')).toHaveCSS(
|
||||
'background-color',
|
||||
'rgba(237, 180, 0, 0.4)',
|
||||
);
|
||||
await otherEditor.getByText('Hello').click();
|
||||
await expect(
|
||||
otherThread.getByText('I can add a comment').first(),
|
||||
).toBeVisible();
|
||||
|
||||
await otherThread
|
||||
.locator('.ProseMirror.bn-editor[contenteditable="true"]')
|
||||
.getByRole('paragraph')
|
||||
.first()
|
||||
.fill('Comment by anonymous user');
|
||||
await otherThread.locator('[data-test="save"]').click();
|
||||
|
||||
await expect(
|
||||
otherThread.getByText('Comment by anonymous user').first(),
|
||||
).toBeVisible();
|
||||
|
||||
await expect(
|
||||
otherThread.getByRole('img', { name: `Anonymous` }).first(),
|
||||
).toBeVisible();
|
||||
|
||||
await otherThread.getByText('Comment by anonymous user').first().hover();
|
||||
await expect(otherThread.locator('[data-test="moreactions"]')).toBeHidden();
|
||||
|
||||
await cleanup();
|
||||
});
|
||||
});
|
||||
@@ -70,6 +70,14 @@ export const keyCloakSignIn = async (
|
||||
await page.click('button[type="submit"]', { force: true });
|
||||
};
|
||||
|
||||
export const getOtherBrowserName = (browserName: BrowserName) => {
|
||||
const otherBrowserName = BROWSERS.find((b) => b !== browserName);
|
||||
if (!otherBrowserName) {
|
||||
throw new Error('No alternative browser found');
|
||||
}
|
||||
return otherBrowserName;
|
||||
};
|
||||
|
||||
export const randomName = (name: string, browserName: string, length: number) =>
|
||||
Array.from({ length }, (_el, index) => {
|
||||
return `${browserName}-${Math.floor(Math.random() * 10000)}-${index}-${name}`;
|
||||
@@ -125,7 +133,9 @@ export const verifyDocName = async (page: Page, docName: string) => {
|
||||
try {
|
||||
await expect(
|
||||
page.getByRole('textbox', { name: 'Document title' }),
|
||||
).toContainText(docName);
|
||||
).toContainText(docName, {
|
||||
timeout: 1000,
|
||||
});
|
||||
} catch {
|
||||
await expect(page.getByRole('heading', { name: docName })).toBeVisible();
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Page, chromium, expect } from '@playwright/test';
|
||||
|
||||
import {
|
||||
BROWSERS,
|
||||
BrowserName,
|
||||
getOtherBrowserName,
|
||||
keyCloakSignIn,
|
||||
verifyDocName,
|
||||
} from './utils-common';
|
||||
@@ -88,21 +88,30 @@ export const updateRoleUser = async (
|
||||
* @param docTitle The title of the document (optional).
|
||||
* @returns An object containing the other browser, context, and page.
|
||||
*/
|
||||
type ConnectOtherUserToDocParams = {
|
||||
docUrl: string;
|
||||
docTitle?: string;
|
||||
withoutSignIn?: boolean;
|
||||
} & (
|
||||
| {
|
||||
otherBrowserName: BrowserName;
|
||||
browserName?: never;
|
||||
}
|
||||
| {
|
||||
browserName: BrowserName;
|
||||
otherBrowserName?: never;
|
||||
}
|
||||
);
|
||||
|
||||
export const connectOtherUserToDoc = async ({
|
||||
browserName,
|
||||
docUrl,
|
||||
docTitle,
|
||||
otherBrowserName: _otherBrowserName,
|
||||
withoutSignIn,
|
||||
}: {
|
||||
browserName: BrowserName;
|
||||
docUrl: string;
|
||||
docTitle?: string;
|
||||
withoutSignIn?: boolean;
|
||||
}) => {
|
||||
const otherBrowserName = BROWSERS.find((b) => b !== browserName);
|
||||
if (!otherBrowserName) {
|
||||
throw new Error('No alternative browser found');
|
||||
}
|
||||
}: ConnectOtherUserToDocParams) => {
|
||||
const otherBrowserName =
|
||||
_otherBrowserName || getOtherBrowserName(browserName);
|
||||
|
||||
const otherBrowser = await chromium.launch({ headless: true });
|
||||
const otherContext = await otherBrowser.newContext({
|
||||
|
||||
Reference in New Issue
Block a user