diff --git a/src/frontend/apps/e2e/__tests__/app-impress/doc-editor.spec.ts b/src/frontend/apps/e2e/__tests__/app-impress/doc-editor.spec.ts
index f64139b7..12da91e8 100644
--- a/src/frontend/apps/e2e/__tests__/app-impress/doc-editor.spec.ts
+++ b/src/frontend/apps/e2e/__tests__/app-impress/doc-editor.spec.ts
@@ -1,14 +1,13 @@
import path from 'path';
-import { expect, test } from '@playwright/test';
+import { chromium, expect, test } from '@playwright/test';
import cs from 'convert-stream';
import {
- CONFIG,
- addNewMember,
createDoc,
goToGridDoc,
mockedDocument,
+ overrideConfig,
verifyDocName,
} from './common';
@@ -522,52 +521,141 @@ test.describe('Doc Editor', () => {
test('it checks block editing when not connected to collab server', async ({
page,
+ browserName,
}) => {
- await page.route('**/api/v1.0/config/', async (route) => {
- const request = route.request();
- if (request.method().includes('GET')) {
- await route.fulfill({
- json: {
- ...CONFIG,
- COLLABORATION_WS_URL: 'ws://localhost:5555/collaboration/ws/',
- COLLABORATION_WS_NOT_CONNECTED_READY_ONLY: true,
- },
- });
- } else {
- await route.continue();
- }
+ /**
+ * The good port is 4444, but we want to simulate a not connected
+ * collaborative server.
+ * So we use a port that is not used by the collaborative server.
+ * The server will not be able to connect to the collaborative server.
+ */
+ await overrideConfig(page, {
+ COLLABORATION_WS_URL: 'ws://localhost:5555/collaboration/ws/',
});
await page.goto('/');
- void page
- .getByRole('button', {
- name: 'New doc',
- })
- .click();
+ const [title] = await createDoc(page, 'editing-blocking', browserName, 1);
const card = page.getByLabel('It is the card information');
await expect(
- card.getByText('Your network do not allow you to edit'),
+ card.getByText('Others are editing. Your network prevent changes.'),
).toBeHidden();
const editor = page.locator('.ProseMirror');
await expect(editor).toHaveAttribute('contenteditable', 'true');
+ let responseCanEditPromise = page.waitForResponse(
+ (response) =>
+ response.url().includes(`/can-edit/`) && response.status() === 200,
+ );
+
await page.getByRole('button', { name: 'Share' }).click();
- await addNewMember(page, 0, 'Editor', 'impress');
+ await page.getByLabel('Visibility', { exact: true }).click();
+
+ await page
+ .getByRole('menuitem', {
+ name: 'Public',
+ })
+ .click();
+
+ await expect(
+ page.getByText('The document visibility has been updated.'),
+ ).toBeVisible();
+
+ await page.getByLabel('Visibility mode').click();
+ await page.getByRole('menuitem', { name: 'Editing' }).click();
// Close the modal
await page.getByRole('button', { name: 'close' }).first().click();
+ let responseCanEdit = await responseCanEditPromise;
+ expect(responseCanEdit.ok()).toBeTruthy();
+ let jsonCanEdit = (await responseCanEdit.json()) as { can_edit: boolean };
+ expect(jsonCanEdit.can_edit).toBeTruthy();
+
+ const urlDoc = page.url();
+
+ /**
+ * We open another browser that will connect to the collaborative server
+ * and will block the current browser to edit the doc.
+ */
+ const otherBrowser = await chromium.launch({ headless: true });
+ const otherContext = await otherBrowser.newContext({
+ locale: 'en-US',
+ timezoneId: 'Europe/Paris',
+ permissions: [],
+ storageState: {
+ cookies: [],
+ origins: [],
+ },
+ });
+ const otherPage = await otherContext.newPage();
+
+ const webSocketPromise = otherPage.waitForEvent(
+ 'websocket',
+ (webSocket) => {
+ return webSocket
+ .url()
+ .includes('ws://localhost:4444/collaboration/ws/?room=');
+ },
+ );
+
+ await otherPage.goto(urlDoc);
+
+ const webSocket = await webSocketPromise;
+ expect(webSocket.url()).toContain(
+ 'ws://localhost:4444/collaboration/ws/?room=',
+ );
+
+ await verifyDocName(otherPage, title);
+
+ await page.reload();
+
+ responseCanEditPromise = page.waitForResponse(
+ (response) =>
+ response.url().includes(`/can-edit/`) && response.status() === 200,
+ );
+
+ responseCanEdit = await responseCanEditPromise;
+ expect(responseCanEdit.ok()).toBeTruthy();
+
+ jsonCanEdit = (await responseCanEdit.json()) as { can_edit: boolean };
+ expect(jsonCanEdit.can_edit).toBeFalsy();
+
await expect(
- card.getByText('Your network do not allow you to edit'),
+ card.getByText('Others are editing. Your network prevent changes.'),
).toBeVisible({
timeout: 10000,
});
await expect(editor).toHaveAttribute('contenteditable', 'false');
+
+ await page.getByRole('button', { name: 'Share' }).click();
+
+ await page.getByLabel('Visibility mode').click();
+ await page.getByRole('menuitem', { name: 'Reading' }).click();
+
+ // Close the modal
+ await page.getByRole('button', { name: 'close' }).first().click();
+
+ await page.reload();
+
+ responseCanEditPromise = page.waitForResponse(
+ (response) =>
+ response.url().includes(`/can-edit/`) && response.status() === 200,
+ );
+
+ responseCanEdit = await responseCanEditPromise;
+ expect(responseCanEdit.ok()).toBeTruthy();
+
+ jsonCanEdit = (await responseCanEdit.json()) as { can_edit: boolean };
+ expect(jsonCanEdit.can_edit).toBeTruthy();
+
+ await expect(
+ card.getByText('Others are editing. Your network prevent changes.'),
+ ).toBeHidden();
});
test('it checks if callout custom block', async ({ page, browserName }) => {
diff --git a/src/frontend/apps/e2e/__tests__/app-impress/doc-header.spec.ts b/src/frontend/apps/e2e/__tests__/app-impress/doc-header.spec.ts
index 0eec2455..feaf18a6 100644
--- a/src/frontend/apps/e2e/__tests__/app-impress/doc-header.spec.ts
+++ b/src/frontend/apps/e2e/__tests__/app-impress/doc-header.spec.ts
@@ -15,49 +15,11 @@ test.beforeEach(async ({ page }) => {
});
test.describe('Doc Header', () => {
- test('it checks the element are correctly displayed', async ({ page }) => {
- await mockedDocument(page, {
- accesses: [
- {
- id: 'b0df4343-c8bd-4c20-9ff6-fbf94fc94egg',
- role: 'owner',
- user: {
- email: 'super@owner.com',
- full_name: 'Super Owner',
- },
- },
- {
- id: 'b0df4343-c8bd-4c20-9ff6-fbf94fc94egg',
- role: 'admin',
- user: {
- email: 'super@admin.com',
- },
- },
- {
- id: 'b0df4343-c8bd-4c20-9ff6-fbf94fc94egg',
- role: 'owner',
- user: {
- email: 'super2@owner.com',
- },
- },
- ],
- abilities: {
- destroy: true, // Means owner
- link_configuration: true,
- versions_destroy: true,
- versions_list: true,
- versions_retrieve: true,
- accesses_manage: true,
- accesses_view: true,
- update: true,
- partial_update: true,
- retrieve: true,
- },
- link_reach: 'public',
- created_at: '2021-09-01T09:00:00Z',
- });
-
- await goToGridDoc(page);
+ test('it checks the element are correctly displayed', async ({
+ page,
+ browserName,
+ }) => {
+ await createDoc(page, 'doc-update', browserName, 1);
const card = page.getByLabel(
'It is the card information about the document.',
@@ -66,6 +28,18 @@ test.describe('Doc Header', () => {
const docTitle = card.getByRole('textbox', { name: 'doc title input' });
await expect(docTitle).toBeVisible();
+ await page.getByRole('button', { name: 'Share' }).click();
+
+ await page.getByLabel('Visibility', { exact: true }).click();
+
+ await page
+ .getByRole('menuitem', {
+ name: 'Public',
+ })
+ .click();
+
+ await page.getByRole('button', { name: 'close' }).first().click();
+
await expect(card.getByText('Public document')).toBeVisible();
await expect(card.getByText('Owner ยท')).toBeVisible();
diff --git a/src/frontend/apps/impress/src/features/docs/doc-editor/components/BlockNoteEditor.tsx b/src/frontend/apps/impress/src/features/docs/doc-editor/components/BlockNoteEditor.tsx
index 9a486b58..fe419b80 100644
--- a/src/frontend/apps/impress/src/features/docs/doc-editor/components/BlockNoteEditor.tsx
+++ b/src/frontend/apps/impress/src/features/docs/doc-editor/components/BlockNoteEditor.tsx
@@ -50,9 +50,10 @@ export const BlockNoteEditor = ({ doc, provider }: BlockNoteEditorProps) => {
const { t } = useTranslation();
const { isEditable, isLoading } = useIsCollaborativeEditable(doc);
+ const isConnectedToCollabServer = provider.isSynced;
const readOnly = !doc.abilities.partial_update || !isEditable || isLoading;
- useSaveDoc(doc.id, provider.document, !readOnly);
+ useSaveDoc(doc.id, provider.document, !readOnly, isConnectedToCollabServer);
const { i18n } = useTranslation();
const lang = i18n.resolvedLanguage;
diff --git a/src/frontend/apps/impress/src/features/docs/doc-editor/hook/__tests__/useSaveDoc.test.tsx b/src/frontend/apps/impress/src/features/docs/doc-editor/hook/__tests__/useSaveDoc.test.tsx
index 0a20001d..a2f41938 100644
--- a/src/frontend/apps/impress/src/features/docs/doc-editor/hook/__tests__/useSaveDoc.test.tsx
+++ b/src/frontend/apps/impress/src/features/docs/doc-editor/hook/__tests__/useSaveDoc.test.tsx
@@ -41,7 +41,7 @@ describe('useSaveDoc', () => {
const addEventListenerSpy = jest.spyOn(window, 'addEventListener');
- renderHook(() => useSaveDoc(docId, yDoc, true), {
+ renderHook(() => useSaveDoc(docId, yDoc, true, true), {
wrapper: AppWrapper,
});
@@ -73,7 +73,7 @@ describe('useSaveDoc', () => {
}),
});
- renderHook(() => useSaveDoc(docId, yDoc, false), {
+ renderHook(() => useSaveDoc(docId, yDoc, false, true), {
wrapper: AppWrapper,
});
@@ -107,7 +107,7 @@ describe('useSaveDoc', () => {
}),
});
- renderHook(() => useSaveDoc(docId, yDoc, true), {
+ renderHook(() => useSaveDoc(docId, yDoc, true, true), {
wrapper: AppWrapper,
});
@@ -143,7 +143,7 @@ describe('useSaveDoc', () => {
}),
});
- renderHook(() => useSaveDoc(docId, yDoc, true), {
+ renderHook(() => useSaveDoc(docId, yDoc, true, true), {
wrapper: AppWrapper,
});
@@ -164,7 +164,7 @@ describe('useSaveDoc', () => {
const docId = 'test-doc-id';
const removeEventListenerSpy = jest.spyOn(window, 'removeEventListener');
- const { unmount } = renderHook(() => useSaveDoc(docId, yDoc, true), {
+ const { unmount } = renderHook(() => useSaveDoc(docId, yDoc, true, true), {
wrapper: AppWrapper,
});
diff --git a/src/frontend/apps/impress/src/features/docs/doc-editor/hook/useSaveDoc.tsx b/src/frontend/apps/impress/src/features/docs/doc-editor/hook/useSaveDoc.tsx
index 274adcff..c6ca782e 100644
--- a/src/frontend/apps/impress/src/features/docs/doc-editor/hook/useSaveDoc.tsx
+++ b/src/frontend/apps/impress/src/features/docs/doc-editor/hook/useSaveDoc.tsx
@@ -10,7 +10,12 @@ import { toBase64 } from '../utils';
const SAVE_INTERVAL = 60000;
-const useSaveDoc = (docId: string, yDoc: Y.Doc, canSave: boolean) => {
+const useSaveDoc = (
+ docId: string,
+ yDoc: Y.Doc,
+ canSave: boolean,
+ isConnectedToCollabServer: boolean,
+) => {
const { mutate: updateDoc } = useUpdateDoc({
listInvalideQueries: [KEY_LIST_DOC_VERSIONS],
onSuccess: () => {
@@ -49,10 +54,18 @@ const useSaveDoc = (docId: string, yDoc: Y.Doc, canSave: boolean) => {
updateDoc({
id: docId,
content: toBase64(Y.encodeStateAsUpdate(yDoc)),
+ websocket: isConnectedToCollabServer,
});
return true;
- }, [canSave, yDoc, docId, isLocalChange, updateDoc]);
+ }, [
+ canSave,
+ isLocalChange,
+ updateDoc,
+ docId,
+ yDoc,
+ isConnectedToCollabServer,
+ ]);
const router = useRouter();
diff --git a/src/frontend/apps/impress/src/features/docs/doc-header/components/AlertNetwork.tsx b/src/frontend/apps/impress/src/features/docs/doc-header/components/AlertNetwork.tsx
index b070356c..dd9cb3eb 100644
--- a/src/frontend/apps/impress/src/features/docs/doc-header/components/AlertNetwork.tsx
+++ b/src/frontend/apps/impress/src/features/docs/doc-header/components/AlertNetwork.tsx
@@ -32,7 +32,7 @@ export const AlertNetwork = () => {
- {t('Your network do not allow you to edit')}
+ {t('Others are editing. Your network prevent changes.')}
{
$margin={{ top: 'auto' }}
/>
- {t('Know more')}
+ {t('Learn more')}
@@ -74,8 +74,8 @@ export const AlertNetworkModal = ({ onClose }: AlertNetworkModalProps) => {
onClose={() => onClose()}
rightActions={
<>
-