From e340463d35edb7f0dfbd53e760ac15a3d5927065 Mon Sep 17 00:00:00 2001 From: Anthony LC Date: Wed, 17 Dec 2025 17:00:47 +0100 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B(frontend)=20fix=20versioning=20con?= =?UTF-8?q?flict?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We switching from one version to the other, depending on the blocks inside, the version editor could crash due to conflicts between the different versions. We now reset the previous content when switching version to avoid these conflicts. --- CHANGELOG.md | 1 + .../__tests__/app-impress/doc-version.spec.ts | 68 +++++++++++++++---- .../doc-versioning/api/useDocVersions.tsx | 1 - .../components/DocVersionEditor.tsx | 9 ++- 4 files changed, 63 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2defd1da..2cb8bb88 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,7 @@ and this project adheres to - 🐛(frontend) paste content with comments from another document #1732 - 🐛(frontend) Select text + Go back one page crash the app #1733 +- 🐛(frontend) fix versioning conflict #1742 ## [4.1.0] - 2025-12-09 diff --git a/src/frontend/apps/e2e/__tests__/app-impress/doc-version.spec.ts b/src/frontend/apps/e2e/__tests__/app-impress/doc-version.spec.ts index b65bc9d0..97777711 100644 --- a/src/frontend/apps/e2e/__tests__/app-impress/doc-version.spec.ts +++ b/src/frontend/apps/e2e/__tests__/app-impress/doc-version.spec.ts @@ -6,6 +6,7 @@ import { mockedDocument, verifyDocName, } from './utils-common'; +import { openSuggestionMenu, writeInEditor } from './utils-editor'; test.beforeEach(async ({ page }) => { await page.goto('/'); @@ -17,6 +18,7 @@ test.describe('Doc Version', () => { await verifyDocName(page, randomDoc); + // Initially, there is no version await page.getByLabel('Open the document options').click(); await page.getByRole('menuitem', { name: 'Version history' }).click(); await expect(page.getByText('History', { exact: true })).toBeVisible(); @@ -26,31 +28,49 @@ test.describe('Doc Version', () => { await expect(panel).toBeVisible(); await expect(modal.getByText('No versions')).toBeVisible(); - const editor = page.locator('.ProseMirror'); await modal.getByRole('button', { name: 'close' }).click(); - await editor.click(); - await page.keyboard.type('# Hello World'); + await writeInEditor({ page, text: 'Hello World' }); + + // It will trigger a save, no version created yet (initial version is not counted) await goToGridDoc(page, { title: randomDoc, }); - await expect( - page.getByRole('heading', { name: 'Hello World' }), - ).toBeVisible(); + await expect(page.getByText('Hello World')).toBeVisible(); - await page - .locator('.ProseMirror .bn-block') - .getByRole('heading', { name: 'Hello World' }) - .fill('It will create a version'); + // Write more + await writeInEditor({ page, text: 'It will create a version' }); + await openSuggestionMenu({ page }); + await page.getByText('Add a callout block').click(); + + const calloutBlock = page + .locator('div[data-content-type="callout"]') + .first(); + + await expect(calloutBlock).toBeVisible(); + + // It will trigger a save and create a version this time await goToGridDoc(page, { title: randomDoc, }); await expect(page.getByText('Hello World')).toBeHidden(); + await expect(page.getByText('It will create a version')).toBeVisible(); + + await expect(calloutBlock).toBeVisible(); + + // Write more + await writeInEditor({ page, text: 'It will create a second version' }); + + // It will trigger a save and create a second version + await goToGridDoc(page, { + title: randomDoc, + }); + await expect( - page.getByRole('heading', { name: 'It will create a version' }), + page.getByText('It will create a second version'), ).toBeVisible(); await page.getByLabel('Open the document options').click(); @@ -60,11 +80,33 @@ test.describe('Doc Version', () => { await expect(page.getByText('History', { exact: true })).toBeVisible(); await expect(page.getByRole('status')).toBeHidden(); const items = await panel.locator('.version-item').all(); - expect(items.length).toBe(1); - await items[0].click(); + expect(items.length).toBe(2); + await items[1].click(); await expect(modal.getByText('Hello World')).toBeVisible(); await expect(modal.getByText('It will create a version')).toBeHidden(); + await expect( + modal.locator('div[data-content-type="callout"]').first(), + ).toBeHidden(); + + await items[0].click(); + + await expect(modal.getByText('Hello World')).toBeVisible(); + await expect(modal.getByText('It will create a version')).toBeVisible(); + await expect( + modal.locator('div[data-content-type="callout"]').first(), + ).toBeVisible(); + await expect( + modal.getByText('It will create a second version'), + ).toBeHidden(); + + await items[1].click(); + + await expect(modal.getByText('Hello World')).toBeVisible(); + await expect(modal.getByText('It will create a version')).toBeHidden(); + await expect( + modal.locator('div[data-content-type="callout"]').first(), + ).toBeHidden(); }); test('it does not display the doc versions if not allowed', async ({ diff --git a/src/frontend/apps/impress/src/features/docs/doc-versioning/api/useDocVersions.tsx b/src/frontend/apps/impress/src/features/docs/doc-versioning/api/useDocVersions.tsx index 3d75f8ce..cd6adcdb 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-versioning/api/useDocVersions.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-versioning/api/useDocVersions.tsx @@ -77,7 +77,6 @@ export function useDocVersionsInfiniteQuery( getNextPageParam(lastPage) { return lastPage.next_version_id_marker || undefined; }, - ...queryConfig, }); } diff --git a/src/frontend/apps/impress/src/features/docs/doc-versioning/components/DocVersionEditor.tsx b/src/frontend/apps/impress/src/features/docs/doc-versioning/components/DocVersionEditor.tsx index fc1f76f9..3bb3c89c 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-versioning/components/DocVersionEditor.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-versioning/components/DocVersionEditor.tsx @@ -32,13 +32,18 @@ export const DocVersionEditor = ({ const { replace } = useRouter(); const [initialContent, setInitialContent] = useState(); + // Reset initialContent when versionId changes to avoid conflicts between versions useEffect(() => { - if (!version?.content) { + setInitialContent(undefined); + }, [versionId]); + + useEffect(() => { + if (!version?.content || isLoading || initialContent) { return; } setInitialContent(base64ToBlocknoteXmlFragment(version.content)); - }, [version?.content]); + }, [versionId, version?.content, isLoading, initialContent]); if (isError && error) { if (error.status === 404) {