Skip to content

Commit 26e7c86

Browse files
committed
✨(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.
1 parent 47f97df commit 26e7c86

File tree

16 files changed

+1210
-27
lines changed

16 files changed

+1210
-27
lines changed

CHANGELOG.md

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ and this project adheres to
99
### Added
1010

1111
- ✨(frontend) add pdf block to the editor #1293
12+
- ✨ Add comments feature to the editor #1330
1213

1314
### Changed
1415

@@ -25,8 +26,10 @@ and this project adheres to
2526
- ♿ remove redundant aria-label on hidden icons and update tests #1432
2627
- ♿ improve semantic structure and aria roles of leftpanel #1431
2728
- ♿ add default background to left panel for better accessibility #1423
29+
- ♿improve NVDA navigation in DocShareModal #1396
2830
- ♿ restyle checked checkboxes: removing strikethrough #1439
2931
- ♿ add h1 for SR on 40X pages and remove alt texts #1438
32+
3033
### Fixed
3134

3235
- 🐛(backend) duplicate sub docs as root for reader users
@@ -35,11 +38,6 @@ and this project adheres to
3538
- 🐛(frontend) fix legacy role computation #1376
3639
- 🐛(frontend) scroll back to top when navigate to a document #1406
3740

38-
### Changed
39-
40-
- ♿(frontend) improve accessibility:
41-
- ♿improve NVDA navigation in DocShareModal #1396
42-
4341
## [3.7.0] - 2025-09-12
4442

4543
### Added
Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
import { expect, test } from '@playwright/test';
2+
3+
import { createDoc, getOtherBrowserName } from './utils-common';
4+
import { writeInEditor } from './utils-editor';
5+
import { addNewMember, connectOtherUserToDoc } from './utils-share';
6+
7+
test.beforeEach(async ({ page }) => {
8+
await page.goto('/');
9+
});
10+
11+
test.describe('Doc Comments', () => {
12+
test('it checks comments with 2 users in real time', async ({
13+
page,
14+
browserName,
15+
}) => {
16+
const [docTitle] = await createDoc(page, 'comment-doc', browserName, 1);
17+
18+
// We share the doc with another user
19+
const otherBrowserName = getOtherBrowserName(browserName);
20+
await page.getByRole('button', { name: 'Share' }).click();
21+
await addNewMember(page, 0, 'Administrator', otherBrowserName);
22+
23+
await expect(
24+
page
25+
.getByRole('listbox', { name: 'Suggestions' })
26+
.getByText(new RegExp(otherBrowserName)),
27+
).toBeVisible();
28+
29+
await page.getByRole('button', { name: 'close' }).click();
30+
31+
// We add a comment with the first user
32+
const editor = await writeInEditor({ page, text: 'Hello World' });
33+
await editor.getByText('Hello').selectText();
34+
await page.getByRole('button', { name: 'Add comment' }).click();
35+
36+
const thread = page.locator('.bn-thread');
37+
await thread.getByRole('paragraph').first().fill('This is a comment');
38+
await thread.locator('[data-test="save"]').click();
39+
await expect(thread.getByText('This is a comment').first()).toBeHidden();
40+
41+
await editor.getByText('Hello').click();
42+
43+
await thread.getByText('This is a comment').first().hover();
44+
45+
// We add a reaction with the first user
46+
await thread.locator('[data-test="addreaction"]').first().click();
47+
await thread.getByRole('button', { name: '👍' }).click();
48+
49+
await expect(thread.getByText('This is a comment').first()).toBeVisible();
50+
await expect(thread.getByText(`E2E ${browserName}`).first()).toBeVisible();
51+
await expect(thread.locator('.bn-comment-reaction')).toHaveText('👍1');
52+
53+
const urlCommentDoc = page.url();
54+
55+
const { otherPage, cleanup } = await connectOtherUserToDoc({
56+
otherBrowserName,
57+
docUrl: urlCommentDoc,
58+
docTitle,
59+
});
60+
61+
const otherEditor = otherPage.locator('.ProseMirror');
62+
await otherEditor.getByText('Hello').click();
63+
const otherThread = otherPage.locator('.bn-thread');
64+
65+
await otherThread.getByText('This is a comment').first().hover();
66+
await otherThread.locator('[data-test="addreaction"]').first().click();
67+
await otherThread.getByRole('button', { name: '👍' }).click();
68+
69+
// We check that the comment made by the first user is visible for the second user
70+
await expect(
71+
otherThread.getByText('This is a comment').first(),
72+
).toBeVisible();
73+
await expect(
74+
otherThread.getByText(`E2E ${browserName}`).first(),
75+
).toBeVisible();
76+
await expect(otherThread.locator('.bn-comment-reaction')).toHaveText('👍2');
77+
78+
// We add a comment with the second user
79+
await otherThread
80+
.getByRole('paragraph')
81+
.last()
82+
.fill('This is a comment from the other user');
83+
await otherThread.locator('[data-test="save"]').click();
84+
85+
// We check that the second user can see the comment he just made
86+
await expect(
87+
otherThread.getByText('This is a comment from the other user').first(),
88+
).toBeVisible();
89+
await expect(
90+
otherThread.getByText(`E2E ${otherBrowserName}`).first(),
91+
).toBeVisible();
92+
93+
// We check that the first user can see the comment made by the second user in real time
94+
await expect(
95+
thread.getByText('This is a comment from the other user').first(),
96+
).toBeVisible();
97+
await expect(
98+
thread.getByText(`E2E ${otherBrowserName}`).first(),
99+
).toBeVisible();
100+
101+
await cleanup();
102+
});
103+
104+
test('it checks the comments interactions', async ({ page, browserName }) => {
105+
await createDoc(page, 'comment-interaction', browserName, 1);
106+
107+
// Checks add react reaction
108+
const editor = page.locator('.ProseMirror');
109+
await editor.locator('.bn-block-outer').last().fill('Hello World');
110+
await editor.getByText('Hello').selectText();
111+
await page.getByRole('button', { name: 'Add comment' }).click();
112+
113+
const thread = page.locator('.bn-thread');
114+
await thread.getByRole('paragraph').first().fill('This is a comment');
115+
await thread.locator('[data-test="save"]').click();
116+
await expect(thread.getByText('This is a comment').first()).toBeHidden();
117+
118+
// Check background color changed
119+
await expect(editor.getByText('Hello')).toHaveCSS(
120+
'background-color',
121+
'rgba(237, 180, 0, 0.4)',
122+
);
123+
await editor.getByText('Hello').click();
124+
125+
await thread.getByText('This is a comment').first().hover();
126+
127+
// We add a reaction with the first user
128+
await thread.locator('[data-test="addreaction"]').first().click();
129+
await thread.getByRole('button', { name: '👍' }).click();
130+
131+
await expect(thread.locator('.bn-comment-reaction')).toHaveText('👍1');
132+
133+
// Edit Comment
134+
await thread.getByText('This is a comment').first().hover();
135+
await thread.locator('[data-test="moreactions"]').first().click();
136+
await thread.getByRole('menuitem', { name: 'Edit comment' }).click();
137+
const commentEditor = thread.getByText('This is a comment').first();
138+
await commentEditor.fill('This is an edited comment');
139+
const saveBtn = thread.getByRole('button', { name: 'Save' });
140+
await saveBtn.click();
141+
await expect(saveBtn).toBeHidden();
142+
await expect(
143+
thread.getByText('This is an edited comment').first(),
144+
).toBeVisible();
145+
await expect(thread.getByText('This is a comment').first()).toBeHidden();
146+
147+
// Add second comment
148+
await thread.getByRole('paragraph').last().fill('This is a second comment');
149+
await thread.getByRole('button', { name: 'Save' }).click();
150+
await expect(
151+
thread.getByText('This is an edited comment').first(),
152+
).toBeVisible();
153+
await expect(
154+
thread.getByText('This is a second comment').first(),
155+
).toBeVisible();
156+
157+
// Delete second comment
158+
await thread.getByText('This is a second comment').first().hover();
159+
await thread.locator('[data-test="moreactions"]').first().click();
160+
await thread.getByRole('menuitem', { name: 'Delete comment' }).click();
161+
await expect(
162+
thread.getByText('This is a second comment').first(),
163+
).toBeHidden();
164+
165+
// Resolve thread
166+
await thread.getByText('This is an edited comment').first().hover();
167+
await thread.locator('[data-test="resolve"]').click();
168+
await expect(thread).toBeHidden();
169+
await expect(editor.getByText('Hello')).toHaveCSS(
170+
'background-color',
171+
'rgba(0, 0, 0, 0)',
172+
);
173+
});
174+
});

src/frontend/apps/e2e/__tests__/app-impress/doc-member-create.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -229,11 +229,11 @@ test.describe('Document create member', () => {
229229
.last()
230230
.fill('Hello World');
231231

232-
const urlDoc = page.url();
232+
const docUrl = page.url();
233233

234234
// Other user will request access
235235
const { otherPage, otherBrowserName, cleanup } =
236-
await connectOtherUserToDoc(browserName, urlDoc);
236+
await connectOtherUserToDoc({ browserName, docUrl });
237237

238238
await expect(
239239
otherPage.getByText('Insufficient access rights to view the document.'),

src/frontend/apps/e2e/__tests__/app-impress/doc-visibility.spec.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -157,12 +157,12 @@ test.describe('Doc Visibility: Restricted', () => {
157157
.last()
158158
.fill('Hello World');
159159

160-
const urlDoc = page.url();
160+
const docUrl = page.url();
161161

162-
const { otherBrowserName, otherPage } = await connectOtherUserToDoc(
162+
const { otherBrowserName, otherPage } = await connectOtherUserToDoc({
163163
browserName,
164-
urlDoc,
165-
);
164+
docUrl,
165+
});
166166

167167
await expect(
168168
otherPage.getByText('Insufficient access rights to view the document.'),

src/frontend/apps/e2e/__tests__/app-impress/utils-common.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,14 @@ export const keyCloakSignIn = async (
6969
await page.click('button[type="submit"]', { force: true });
7070
};
7171

72+
export const getOtherBrowserName = (browserName: BrowserName) => {
73+
const otherBrowserName = BROWSERS.find((b) => b !== browserName);
74+
if (!otherBrowserName) {
75+
throw new Error('No alternative browser found');
76+
}
77+
return otherBrowserName;
78+
};
79+
7280
export const randomName = (name: string, browserName: string, length: number) =>
7381
Array.from({ length }, (_el, index) => {
7482
return `${browserName}-${Math.floor(Math.random() * 10000)}-${index}-${name}`;

src/frontend/apps/e2e/__tests__/app-impress/utils-share.ts

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import { Page, chromium, expect } from '@playwright/test';
22

33
import {
4-
BROWSERS,
54
BrowserName,
5+
getOtherBrowserName,
66
keyCloakSignIn,
77
verifyDocName,
88
} from './utils-common';
@@ -88,15 +88,28 @@ export const updateRoleUser = async (
8888
* @param docTitle The title of the document (optional).
8989
* @returns An object containing the other browser, context, and page.
9090
*/
91-
export const connectOtherUserToDoc = async (
92-
browserName: BrowserName,
93-
docUrl: string,
94-
docTitle?: string,
95-
) => {
96-
const otherBrowserName = BROWSERS.find((b) => b !== browserName);
97-
if (!otherBrowserName) {
98-
throw new Error('No alternative browser found');
99-
}
91+
type ConnectOtherUserToDocParams = {
92+
docUrl: string;
93+
docTitle?: string;
94+
} & (
95+
| {
96+
otherBrowserName: BrowserName;
97+
browserName?: never;
98+
}
99+
| {
100+
browserName: BrowserName;
101+
otherBrowserName?: never;
102+
}
103+
);
104+
105+
export const connectOtherUserToDoc = async ({
106+
browserName,
107+
docUrl,
108+
docTitle,
109+
otherBrowserName: _otherBrowserName,
110+
}: ConnectOtherUserToDocParams) => {
111+
const otherBrowserName =
112+
_otherBrowserName || getOtherBrowserName(browserName);
100113

101114
const otherBrowser = await chromium.launch({ headless: true });
102115
const otherContext = await otherBrowser.newContext({

src/frontend/apps/impress/src/features/auth/api/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,5 @@ export interface User {
1313
short_name: string;
1414
language?: string;
1515
}
16+
17+
export type UserLight = Pick<User, 'full_name' | 'short_name'>;

0 commit comments

Comments
 (0)