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 c5da0d51..a39362e2 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 @@ -40,19 +40,18 @@ test.describe('Doc Editor', () => { await expect(page.locator('h2').getByText(randomDoc[0])).toBeVisible(); - await page.locator('.ProseMirror.bn-editor').click(); - await page - .locator('.ProseMirror.bn-editor') - .fill('[test markdown](http://test-markdown.html)'); + const editor = page.locator('.ProseMirror'); + await editor.click(); + await editor.fill('[test markdown](http://test-markdown.html)'); - await expect(page.getByText('[test markdown]')).toBeVisible(); + await expect(editor.getByText('[test markdown]')).toBeVisible(); - await page.getByText('[test markdown]').dblclick(); + await editor.getByText('[test markdown]').dblclick(); await page.locator('button[data-test="convertMarkdown"]').click(); - await expect(page.getByText('[test markdown]')).toBeHidden(); + await expect(editor.getByText('[test markdown]')).toBeHidden(); await expect( - page.getByRole('link', { + editor.getByRole('link', { name: 'test markdown', }), ).toHaveAttribute('href', 'http://test-markdown.html'); @@ -64,38 +63,40 @@ test.describe('Doc Editor', () => { // Check the first doc const firstDoc = await goToGridDoc(page); await expect(page.locator('h2').getByText(firstDoc)).toBeVisible(); - await page.locator('.ProseMirror.bn-editor').click(); - await page.locator('.ProseMirror.bn-editor').fill('Hello World Doc 1'); - await expect(page.getByText('Hello World Doc 1')).toBeVisible(); + + const editor = page.locator('.ProseMirror'); + await editor.click(); + await editor.fill('Hello World Doc 1'); + await expect(editor.getByText('Hello World Doc 1')).toBeVisible(); // Check the second doc const secondDoc = await goToGridDoc(page, { nthRow: 2, }); await expect(page.locator('h2').getByText(secondDoc)).toBeVisible(); - await expect(page.getByText('Hello World Doc 1')).toBeHidden(); - await page.locator('.ProseMirror.bn-editor').click(); - await page.locator('.ProseMirror.bn-editor').fill('Hello World Doc 2'); - await expect(page.getByText('Hello World Doc 2')).toBeVisible(); + await expect(editor.getByText('Hello World Doc 1')).toBeHidden(); + await editor.click(); + await editor.fill('Hello World Doc 2'); + await expect(editor.getByText('Hello World Doc 2')).toBeVisible(); // Check the first doc again await goToGridDoc(page, { title: firstDoc, }); await expect(page.locator('h2').getByText(firstDoc)).toBeVisible(); - await expect(page.getByText('Hello World Doc 2')).toBeHidden(); - await expect(page.getByText('Hello World Doc 1')).toBeVisible(); + await expect(editor.getByText('Hello World Doc 2')).toBeHidden(); + await expect(editor.getByText('Hello World Doc 1')).toBeVisible(); }); test('it saves the doc when we change pages', async ({ page }) => { // Check the first doc const doc = await goToGridDoc(page); await expect(page.locator('h2').getByText(doc)).toBeVisible(); - await page.locator('.ProseMirror.bn-editor').click(); - await page - .locator('.ProseMirror.bn-editor') - .fill('Hello World Doc persisted 1'); - await expect(page.getByText('Hello World Doc persisted 1')).toBeVisible(); + + const editor = page.locator('.ProseMirror'); + await editor.click(); + await editor.fill('Hello World Doc persisted 1'); + await expect(editor.getByText('Hello World Doc persisted 1')).toBeVisible(); const secondDoc = await goToGridDoc(page, { nthRow: 2, @@ -107,7 +108,7 @@ test.describe('Doc Editor', () => { title: doc, }); - await expect(page.getByText('Hello World Doc persisted 1')).toBeVisible(); + await expect(editor.getByText('Hello World Doc persisted 1')).toBeVisible(); }); test('it saves the doc when we quit pages', async ({ page, browserName }) => { @@ -117,11 +118,11 @@ test.describe('Doc Editor', () => { // Check the first doc const doc = await goToGridDoc(page); await expect(page.locator('h2').getByText(doc)).toBeVisible(); - await page.locator('.ProseMirror.bn-editor').click(); - await page - .locator('.ProseMirror.bn-editor') - .fill('Hello World Doc persisted 2'); - await expect(page.getByText('Hello World Doc persisted 2')).toBeVisible(); + + const editor = page.locator('.ProseMirror'); + await editor.click(); + await editor.fill('Hello World Doc persisted 2'); + await expect(editor.getByText('Hello World Doc persisted 2')).toBeVisible(); await page.goto('/'); @@ -129,7 +130,7 @@ test.describe('Doc Editor', () => { title: doc, }); - await expect(page.getByText('Hello World Doc persisted 2')).toBeVisible(); + await expect(editor.getByText('Hello World Doc persisted 2')).toBeVisible(); }); test('it cannot edit if viewer', async ({ page }) => { 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 208eeb7e..ced6677f 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 @@ -69,13 +69,64 @@ test.describe('Doc Header', () => { const [randomDoc] = await createDoc(page, 'doc-update', browserName, 1); await page.getByRole('heading', { name: randomDoc }).fill(' '); - await page.getByText('Created at ').click(); + await page.getByText('Created at').click(); await expect( page.getByRole('heading', { name: 'Untitled document' }), ).toBeVisible(); }); + test('it updates the title doc from editor heading', async ({ page }) => { + await page + .getByRole('button', { + name: 'Create a new document', + }) + .click(); + + const docHeader = page.getByLabel( + 'It is the card information about the document.', + ); + + await expect( + docHeader.getByRole('heading', { name: 'Untitled document', level: 2 }), + ).toBeVisible(); + + const editor = page.locator('.ProseMirror'); + + await editor.locator('h1').click(); + await page.keyboard.type('Hello World', { delay: 100 }); + + await expect( + docHeader.getByRole('heading', { name: 'Hello World', level: 2 }), + ).toBeVisible(); + + await expect( + page.getByText('Document title updated successfully'), + ).toBeVisible(); + + await docHeader + .getByRole('heading', { name: 'Hello World', level: 2 }) + .fill('Top World'); + + await editor.locator('h1').fill('Super World'); + + await expect( + docHeader.getByRole('heading', { name: 'Top World', level: 2 }), + ).toBeVisible(); + + await editor.locator('h1').fill(''); + + await docHeader + .getByRole('heading', { name: 'Top World', level: 2 }) + .fill(' '); + + await page.getByText('Created at').click(); + + await expect( + docHeader.getByRole('heading', { name: 'Untitled document', level: 2 }), + ).toBeVisible(); + }); + test('it deletes the doc', async ({ page, browserName }) => { const [randomDoc] = await createDoc(page, 'doc-delete', browserName, 1); await expect(page.locator('h2').getByText(randomDoc)).toBeVisible(); 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 e853dda5..bca0fc38 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 @@ -105,7 +105,8 @@ test.describe('Doc Version', () => { title: randomDoc, }); - await expect(page.getByText('Hello')).toBeVisible(); + const editor = page.locator('.ProseMirror'); + await expect(editor.getByText('Hello')).toBeVisible(); await page.locator('.bn-block-outer').last().click(); await page.keyboard.press('Enter'); await page.locator('.bn-block-outer').last().fill('World'); @@ -153,23 +154,24 @@ test.describe('Doc Version', () => { await expect(page.locator('h2').getByText(randomDoc)).toBeVisible(); - await page.locator('.bn-block-outer').last().click(); - await page.locator('.bn-block-outer').last().fill('Hello'); + const editor = page.locator('.ProseMirror'); + await editor.locator('.bn-block-outer').last().click(); + await editor.locator('.bn-block-outer').last().fill('Hello'); await goToGridDoc(page, { title: randomDoc, }); - await expect(page.getByText('Hello')).toBeVisible(); - await page.locator('.bn-block-outer').last().click(); + await expect(editor.getByText('Hello')).toBeVisible(); + await editor.locator('.bn-block-outer').last().click(); await page.keyboard.press('Enter'); - await page.locator('.bn-block-outer').last().fill('World'); + await editor.locator('.bn-block-outer').last().fill('World'); await goToGridDoc(page, { title: randomDoc, }); - await expect(page.getByText('World')).toBeVisible(); + await expect(editor.getByText('World')).toBeVisible(); await page.getByLabel('Open the document options').click(); await page @@ -180,7 +182,7 @@ test.describe('Doc Version', () => { const panel = page.getByLabel('Document panel'); await panel.locator('li').nth(1).click(); - await expect(page.getByText('World')).toBeHidden(); + await expect(editor.getByText('World')).toBeHidden(); await page .getByRole('button', { @@ -199,7 +201,7 @@ test.describe('Doc Version', () => { await expect(panel.locator('li')).toHaveCount(3); await panel.getByText('Current version').click(); - await expect(page.getByText('Hello')).toBeVisible(); - await expect(page.getByText('World')).toBeHidden(); + await expect(editor.getByText('Hello')).toBeVisible(); + await expect(editor.getByText('World')).toBeHidden(); }); }); diff --git a/src/frontend/apps/impress/src/features/docs/doc-header/components/DocTitle.tsx b/src/frontend/apps/impress/src/features/docs/doc-header/components/DocTitle.tsx index bebd55e4..09980586 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-header/components/DocTitle.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-header/components/DocTitle.tsx @@ -5,12 +5,13 @@ import { VariantType, useToastProvider, } from '@openfun/cunningham-react'; -import React, { useState } from 'react'; +import React, { useCallback, useEffect, useRef, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { createGlobalStyle } from 'styled-components'; import { Box, Text } from '@/components'; import { useCunninghamTheme } from '@/cunningham'; +import { useHeadingStore } from '@/features/docs/doc-editor'; import { Doc, KEY_DOC, @@ -49,38 +50,41 @@ const DocTitleInput = ({ doc }: DocTitleProps) => { const { toast } = useToastProvider(); const { untitledDocument } = useTrans(); const isUntitled = titleDisplay === untitledDocument; + const { headings } = useHeadingStore(); + const headingText = headings?.[0]?.contentText; + const debounceRef = useRef(); const { mutate: updateDoc } = useUpdateDoc({ listInvalideQueries: [KEY_DOC, KEY_LIST_DOC], + onSuccess(data) { + if (data.title !== untitledDocument) { + toast(t('Document title updated successfully'), VariantType.SUCCESS); + } + }, }); - const handleTitleSubmit = (inputText: string) => { - let sanitizedTitle = inputText.trim(); - sanitizedTitle = sanitizedTitle.replace(/(\r\n|\n|\r)/gm, ''); + const handleTitleSubmit = useCallback( + (inputText: string) => { + let sanitizedTitle = inputText.trim(); + sanitizedTitle = sanitizedTitle.replace(/(\r\n|\n|\r)/gm, ''); - // When blank we set to untitled - if (!sanitizedTitle) { - sanitizedTitle = untitledDocument; - setTitleDisplay(sanitizedTitle); - } + // When blank we set to untitled + if (!sanitizedTitle) { + sanitizedTitle = untitledDocument; + setTitleDisplay(sanitizedTitle); + } - // If mutation we update - if (sanitizedTitle !== doc.title) { - updateDoc( - { id: doc.id, title: sanitizedTitle }, - { - onSuccess: () => { - if (sanitizedTitle !== untitledDocument) { - toast( - t('Document title updated successfully'), - VariantType.SUCCESS, - ); - } - }, - }, - ); - } - }; + // If mutation we update + if (sanitizedTitle !== doc.title) { + if (debounceRef.current) { + clearTimeout(debounceRef.current); + debounceRef.current = undefined; + } + updateDoc({ id: doc.id, title: sanitizedTitle }); + } + }, + [doc.id, doc.title, untitledDocument, updateDoc], + ); const handleKeyDown = (e: React.KeyboardEvent) => { if (e.key === 'Enter') { @@ -95,6 +99,23 @@ const DocTitleInput = ({ doc }: DocTitleProps) => { } }; + useEffect(() => { + if ((!debounceRef.current && !isUntitled) || !headingText) { + return; + } + + setTitleDisplay(headingText); + + if (debounceRef.current) { + clearTimeout(debounceRef.current); + } + + debounceRef.current = setTimeout(() => { + handleTitleSubmit(headingText); + debounceRef.current = undefined; + }, 3000); + }, [isUntitled, handleTitleSubmit, headingText]); + return ( <>