Skip to content

Commit be3e80a

Browse files
AntoLClunika
authored andcommitted
🛂(frontend) block edition only when not alone
We added a system to know if a user is alone on a document or not. We adapt the frontend to block the edition only when the user is not alone on the document.
1 parent 45f18db commit be3e80a

File tree

9 files changed

+252
-116
lines changed

9 files changed

+252
-116
lines changed

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

Lines changed: 112 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
import path from 'path';
22

3-
import { expect, test } from '@playwright/test';
3+
import { chromium, expect, test } from '@playwright/test';
44
import cs from 'convert-stream';
55

66
import {
7-
CONFIG,
8-
addNewMember,
97
createDoc,
108
goToGridDoc,
119
mockedDocument,
10+
overrideConfig,
1211
verifyDocName,
1312
} from './common';
1413

@@ -522,52 +521,141 @@ test.describe('Doc Editor', () => {
522521

523522
test('it checks block editing when not connected to collab server', async ({
524523
page,
524+
browserName,
525525
}) => {
526-
await page.route('**/api/v1.0/config/', async (route) => {
527-
const request = route.request();
528-
if (request.method().includes('GET')) {
529-
await route.fulfill({
530-
json: {
531-
...CONFIG,
532-
COLLABORATION_WS_URL: 'ws://localhost:5555/collaboration/ws/',
533-
COLLABORATION_WS_NOT_CONNECTED_READY_ONLY: true,
534-
},
535-
});
536-
} else {
537-
await route.continue();
538-
}
526+
/**
527+
* The good port is 4444, but we want to simulate a not connected
528+
* collaborative server.
529+
* So we use a port that is not used by the collaborative server.
530+
* The server will not be able to connect to the collaborative server.
531+
*/
532+
await overrideConfig(page, {
533+
COLLABORATION_WS_URL: 'ws://localhost:5555/collaboration/ws/',
539534
});
540535

541536
await page.goto('/');
542537

543-
void page
544-
.getByRole('button', {
545-
name: 'New doc',
546-
})
547-
.click();
538+
const [title] = await createDoc(page, 'editing-blocking', browserName, 1);
548539

549540
const card = page.getByLabel('It is the card information');
550541
await expect(
551-
card.getByText('Your network do not allow you to edit'),
542+
card.getByText('Others are editing. Your network prevent changes.'),
552543
).toBeHidden();
553544
const editor = page.locator('.ProseMirror');
554545

555546
await expect(editor).toHaveAttribute('contenteditable', 'true');
556547

548+
let responseCanEditPromise = page.waitForResponse(
549+
(response) =>
550+
response.url().includes(`/can-edit/`) && response.status() === 200,
551+
);
552+
557553
await page.getByRole('button', { name: 'Share' }).click();
558554

559-
await addNewMember(page, 0, 'Editor', 'impress');
555+
await page.getByLabel('Visibility', { exact: true }).click();
556+
557+
await page
558+
.getByRole('menuitem', {
559+
name: 'Public',
560+
})
561+
.click();
562+
563+
await expect(
564+
page.getByText('The document visibility has been updated.'),
565+
).toBeVisible();
566+
567+
await page.getByLabel('Visibility mode').click();
568+
await page.getByRole('menuitem', { name: 'Editing' }).click();
560569

561570
// Close the modal
562571
await page.getByRole('button', { name: 'close' }).first().click();
563572

573+
let responseCanEdit = await responseCanEditPromise;
574+
expect(responseCanEdit.ok()).toBeTruthy();
575+
let jsonCanEdit = (await responseCanEdit.json()) as { can_edit: boolean };
576+
expect(jsonCanEdit.can_edit).toBeTruthy();
577+
578+
const urlDoc = page.url();
579+
580+
/**
581+
* We open another browser that will connect to the collaborative server
582+
* and will block the current browser to edit the doc.
583+
*/
584+
const otherBrowser = await chromium.launch({ headless: true });
585+
const otherContext = await otherBrowser.newContext({
586+
locale: 'en-US',
587+
timezoneId: 'Europe/Paris',
588+
permissions: [],
589+
storageState: {
590+
cookies: [],
591+
origins: [],
592+
},
593+
});
594+
const otherPage = await otherContext.newPage();
595+
596+
const webSocketPromise = otherPage.waitForEvent(
597+
'websocket',
598+
(webSocket) => {
599+
return webSocket
600+
.url()
601+
.includes('ws://localhost:4444/collaboration/ws/?room=');
602+
},
603+
);
604+
605+
await otherPage.goto(urlDoc);
606+
607+
const webSocket = await webSocketPromise;
608+
expect(webSocket.url()).toContain(
609+
'ws://localhost:4444/collaboration/ws/?room=',
610+
);
611+
612+
await verifyDocName(otherPage, title);
613+
614+
await page.reload();
615+
616+
responseCanEditPromise = page.waitForResponse(
617+
(response) =>
618+
response.url().includes(`/can-edit/`) && response.status() === 200,
619+
);
620+
621+
responseCanEdit = await responseCanEditPromise;
622+
expect(responseCanEdit.ok()).toBeTruthy();
623+
624+
jsonCanEdit = (await responseCanEdit.json()) as { can_edit: boolean };
625+
expect(jsonCanEdit.can_edit).toBeFalsy();
626+
564627
await expect(
565-
card.getByText('Your network do not allow you to edit'),
628+
card.getByText('Others are editing. Your network prevent changes.'),
566629
).toBeVisible({
567630
timeout: 10000,
568631
});
569632

570633
await expect(editor).toHaveAttribute('contenteditable', 'false');
634+
635+
await page.getByRole('button', { name: 'Share' }).click();
636+
637+
await page.getByLabel('Visibility mode').click();
638+
await page.getByRole('menuitem', { name: 'Reading' }).click();
639+
640+
// Close the modal
641+
await page.getByRole('button', { name: 'close' }).first().click();
642+
643+
await page.reload();
644+
645+
responseCanEditPromise = page.waitForResponse(
646+
(response) =>
647+
response.url().includes(`/can-edit/`) && response.status() === 200,
648+
);
649+
650+
responseCanEdit = await responseCanEditPromise;
651+
expect(responseCanEdit.ok()).toBeTruthy();
652+
653+
jsonCanEdit = (await responseCanEdit.json()) as { can_edit: boolean };
654+
expect(jsonCanEdit.can_edit).toBeTruthy();
655+
656+
await expect(
657+
card.getByText('Others are editing. Your network prevent changes.'),
658+
).toBeHidden();
571659
});
572660

573661
test('it checks if callout custom block', async ({ page, browserName }) => {

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

Lines changed: 17 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -15,49 +15,11 @@ test.beforeEach(async ({ page }) => {
1515
});
1616

1717
test.describe('Doc Header', () => {
18-
test('it checks the element are correctly displayed', async ({ page }) => {
19-
await mockedDocument(page, {
20-
accesses: [
21-
{
22-
id: 'b0df4343-c8bd-4c20-9ff6-fbf94fc94egg',
23-
role: 'owner',
24-
user: {
25-
26-
full_name: 'Super Owner',
27-
},
28-
},
29-
{
30-
id: 'b0df4343-c8bd-4c20-9ff6-fbf94fc94egg',
31-
role: 'admin',
32-
user: {
33-
34-
},
35-
},
36-
{
37-
id: 'b0df4343-c8bd-4c20-9ff6-fbf94fc94egg',
38-
role: 'owner',
39-
user: {
40-
41-
},
42-
},
43-
],
44-
abilities: {
45-
destroy: true, // Means owner
46-
link_configuration: true,
47-
versions_destroy: true,
48-
versions_list: true,
49-
versions_retrieve: true,
50-
accesses_manage: true,
51-
accesses_view: true,
52-
update: true,
53-
partial_update: true,
54-
retrieve: true,
55-
},
56-
link_reach: 'public',
57-
created_at: '2021-09-01T09:00:00Z',
58-
});
59-
60-
await goToGridDoc(page);
18+
test('it checks the element are correctly displayed', async ({
19+
page,
20+
browserName,
21+
}) => {
22+
await createDoc(page, 'doc-update', browserName, 1);
6123

6224
const card = page.getByLabel(
6325
'It is the card information about the document.',
@@ -66,6 +28,18 @@ test.describe('Doc Header', () => {
6628
const docTitle = card.getByRole('textbox', { name: 'doc title input' });
6729
await expect(docTitle).toBeVisible();
6830

31+
await page.getByRole('button', { name: 'Share' }).click();
32+
33+
await page.getByLabel('Visibility', { exact: true }).click();
34+
35+
await page
36+
.getByRole('menuitem', {
37+
name: 'Public',
38+
})
39+
.click();
40+
41+
await page.getByRole('button', { name: 'close' }).first().click();
42+
6943
await expect(card.getByText('Public document')).toBeVisible();
7044

7145
await expect(card.getByText('Owner ·')).toBeVisible();

src/frontend/apps/impress/src/features/docs/doc-editor/components/BlockNoteEditor.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,10 @@ export const BlockNoteEditor = ({ doc, provider }: BlockNoteEditorProps) => {
5050
const { t } = useTranslation();
5151

5252
const { isEditable, isLoading } = useIsCollaborativeEditable(doc);
53+
const isConnectedToCollabServer = provider.isSynced;
5354
const readOnly = !doc.abilities.partial_update || !isEditable || isLoading;
5455

55-
useSaveDoc(doc.id, provider.document, !readOnly);
56+
useSaveDoc(doc.id, provider.document, !readOnly, isConnectedToCollabServer);
5657
const { i18n } = useTranslation();
5758
const lang = i18n.resolvedLanguage;
5859

src/frontend/apps/impress/src/features/docs/doc-editor/hook/__tests__/useSaveDoc.test.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ describe('useSaveDoc', () => {
4141

4242
const addEventListenerSpy = jest.spyOn(window, 'addEventListener');
4343

44-
renderHook(() => useSaveDoc(docId, yDoc, true), {
44+
renderHook(() => useSaveDoc(docId, yDoc, true, true), {
4545
wrapper: AppWrapper,
4646
});
4747

@@ -73,7 +73,7 @@ describe('useSaveDoc', () => {
7373
}),
7474
});
7575

76-
renderHook(() => useSaveDoc(docId, yDoc, false), {
76+
renderHook(() => useSaveDoc(docId, yDoc, false, true), {
7777
wrapper: AppWrapper,
7878
});
7979

@@ -107,7 +107,7 @@ describe('useSaveDoc', () => {
107107
}),
108108
});
109109

110-
renderHook(() => useSaveDoc(docId, yDoc, true), {
110+
renderHook(() => useSaveDoc(docId, yDoc, true, true), {
111111
wrapper: AppWrapper,
112112
});
113113

@@ -143,7 +143,7 @@ describe('useSaveDoc', () => {
143143
}),
144144
});
145145

146-
renderHook(() => useSaveDoc(docId, yDoc, true), {
146+
renderHook(() => useSaveDoc(docId, yDoc, true, true), {
147147
wrapper: AppWrapper,
148148
});
149149

@@ -164,7 +164,7 @@ describe('useSaveDoc', () => {
164164
const docId = 'test-doc-id';
165165
const removeEventListenerSpy = jest.spyOn(window, 'removeEventListener');
166166

167-
const { unmount } = renderHook(() => useSaveDoc(docId, yDoc, true), {
167+
const { unmount } = renderHook(() => useSaveDoc(docId, yDoc, true, true), {
168168
wrapper: AppWrapper,
169169
});
170170

src/frontend/apps/impress/src/features/docs/doc-editor/hook/useSaveDoc.tsx

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,12 @@ import { toBase64 } from '../utils';
1010

1111
const SAVE_INTERVAL = 60000;
1212

13-
const useSaveDoc = (docId: string, yDoc: Y.Doc, canSave: boolean) => {
13+
const useSaveDoc = (
14+
docId: string,
15+
yDoc: Y.Doc,
16+
canSave: boolean,
17+
isConnectedToCollabServer: boolean,
18+
) => {
1419
const { mutate: updateDoc } = useUpdateDoc({
1520
listInvalideQueries: [KEY_LIST_DOC_VERSIONS],
1621
onSuccess: () => {
@@ -49,10 +54,18 @@ const useSaveDoc = (docId: string, yDoc: Y.Doc, canSave: boolean) => {
4954
updateDoc({
5055
id: docId,
5156
content: toBase64(Y.encodeStateAsUpdate(yDoc)),
57+
websocket: isConnectedToCollabServer,
5258
});
5359

5460
return true;
55-
}, [canSave, yDoc, docId, isLocalChange, updateDoc]);
61+
}, [
62+
canSave,
63+
isLocalChange,
64+
updateDoc,
65+
docId,
66+
yDoc,
67+
isConnectedToCollabServer,
68+
]);
5669

5770
const router = useRouter();
5871

0 commit comments

Comments
 (0)