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 3cc3fee6..9b5173d8 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 @@ -307,6 +307,11 @@ test.describe('Doc Editor', () => { } }); + // Ensure AI feature is enabled + await overrideConfig(page, { + AI_FEATURE_ENABLED: true, + }); + await createDoc(page, 'doc-ai', browserName, 1); await page.locator('.bn-block-outer').last().fill('Hello World'); @@ -314,7 +319,7 @@ test.describe('Doc Editor', () => { const editor = page.locator('.ProseMirror'); await editor.getByText('Hello').selectText(); - await page.getByRole('button', { name: 'AI' }).click(); + await page.locator('[data-test="ai-actions"]').click(); await expect( page.getByRole('menuitem', { name: 'Use as prompt' }), @@ -400,11 +405,11 @@ test.describe('Doc Editor', () => { /* eslint-disable playwright/no-conditional-expect */ /* eslint-disable playwright/no-conditional-in-test */ if (!ai_transform && !ai_translate) { - await expect(page.getByRole('button', { name: 'AI' })).toBeHidden(); + await expect(page.locator('[data-test="ai-actions"]')).toBeHidden(); return; } - await page.getByRole('button', { name: 'AI' }).click(); + await page.locator('[data-test="ai-actions"]').click(); if (ai_transform) { await expect( diff --git a/src/frontend/apps/e2e/__tests__/app-impress/doc-search.spec.ts b/src/frontend/apps/e2e/__tests__/app-impress/doc-search.spec.ts index 412064dd..9fe07578 100644 --- a/src/frontend/apps/e2e/__tests__/app-impress/doc-search.spec.ts +++ b/src/frontend/apps/e2e/__tests__/app-impress/doc-search.spec.ts @@ -175,10 +175,10 @@ test.describe('Document search', () => { // Expect to find the first doc await expect( - page.getByRole('presentation').getByLabel(firstDocTitle), + page.getByRole('presentation').getByText(firstDocTitle), ).toBeVisible(); await expect( - page.getByRole('presentation').getByLabel(secondDocTitle), + page.getByRole('presentation').getByText(secondDocTitle), ).toBeVisible(); await page.getByRole('button', { name: 'close' }).click(); @@ -196,13 +196,13 @@ test.describe('Document search', () => { // Now there is a sub page - expect to have the focus on the current doc await expect( - page.getByRole('presentation').getByLabel(secondDocTitle), + page.getByRole('presentation').getByText(secondDocTitle), ).toBeVisible(); await expect( - page.getByRole('presentation').getByLabel(secondChildDocTitle), + page.getByRole('presentation').getByText(secondChildDocTitle), ).toBeVisible(); await expect( - page.getByRole('presentation').getByLabel(firstDocTitle), + page.getByRole('presentation').getByText(firstDocTitle), ).toBeHidden(); }); }); diff --git a/src/frontend/apps/e2e/__tests__/app-impress/doc-tree.spec.ts b/src/frontend/apps/e2e/__tests__/app-impress/doc-tree.spec.ts index a3d52c16..214b20d3 100644 --- a/src/frontend/apps/e2e/__tests__/app-impress/doc-tree.spec.ts +++ b/src/frontend/apps/e2e/__tests__/app-impress/doc-tree.spec.ts @@ -9,7 +9,6 @@ import { updateDocTitle, verifyDocName, } from './utils-common'; -import { addNewMember } from './utils-share'; import { clickOnAddRootSubPage, createRootSubPage } from './utils-sub-pages'; test.describe('Doc Tree', () => { @@ -185,14 +184,13 @@ test.describe('Doc Tree', () => { const docTree = page.getByTestId('doc-tree'); await expect(docTree.getByText(docChild)).toBeVisible(); await docTree.click(); - const child = docTree - .getByRole('treeitem') - .locator('.--docs-sub-page-item') - .filter({ - hasText: docChild, - }); + const child = docTree.locator('.--docs-sub-page-item').filter({ + hasText: docChild, + }); await child.hover(); + // Wait a bit for the hover effect to take place const menu = child.getByText(`more_horiz`); + await expect(menu).toBeVisible(); await menu.click(); await page.getByText('Move to my docs').click(); @@ -215,43 +213,38 @@ test.describe('Doc Tree', () => { await verifyDocName(page, docParent); - await page.getByRole('button', { name: 'Share' }).click(); - - await addNewMember(page, 0, 'Owner', 'impress'); - - const list = page.getByTestId('doc-share-quick-search'); - const currentUser = list.getByTestId( - `doc-share-member-row-user@${browserName}.test`, - ); - const currentUserRole = currentUser.getByLabel('doc-role-dropdown'); - await currentUserRole.click(); - await page.getByLabel('Administrator').click(); - await list.click(); - - await page.getByRole('button', { name: 'Ok' }).click(); - + // Create a child document first const { name: docChild } = await createRootSubPage( page, browserName, 'doc-tree-detach-child', ); + // Now try to detach the child document - this should work for the owner const docTree = page.getByTestId('doc-tree'); await expect(docTree.getByText(docChild)).toBeVisible(); await docTree.click(); - const child = docTree - .getByRole('treeitem') - .locator('.--docs-sub-page-item') - .filter({ - hasText: docChild, - }); + const child = docTree.locator('.--docs-sub-page-item').filter({ + hasText: docChild, + }); await child.hover(); + // Wait a bit for the hover effect to take place const menu = child.getByText(`more_horiz`); + await expect(menu).toBeVisible(); await menu.click(); + // The owner should be able to detach the document + await page.getByRole('menuitem', { name: 'Move to my docs' }).click(); + + // Verify the document was detached - it should no longer be in the current tree await expect( - page.getByRole('menuitem', { name: 'Move to my docs' }), - ).toHaveAttribute('aria-disabled', 'true'); + page.getByRole('textbox', { name: 'doc title input' }), + ).not.toHaveText(docChild); + + // Verify the document is now on the home page + const header = page.locator('header').first(); + await header.locator('h1').getByText('Docs').click(); + await expect(page.getByText(docChild)).toBeVisible(); }); }); diff --git a/src/frontend/apps/impress/src/components/dropdown-menu/hook/useActionableMode.ts b/src/frontend/apps/impress/src/components/dropdown-menu/hook/useActionableMode.ts index 784250cb..7c807e4c 100644 --- a/src/frontend/apps/impress/src/components/dropdown-menu/hook/useActionableMode.ts +++ b/src/frontend/apps/impress/src/components/dropdown-menu/hook/useActionableMode.ts @@ -10,10 +10,6 @@ type FocusableNode = NodeRendererProps>['node'] & { /** * Hook to manage keyboard navigation for actionable items in a tree view. * - * Provides two modes: - * 1. Activation: F2/Enter moves focus to first actionable element - * 2. Navigation: Arrow keys navigate between actions, Escape returns to tree node - * * Disables navigation when dropdown menu is open to prevent conflicts. */ export const useActionableMode = ( @@ -23,11 +19,21 @@ export const useActionableMode = ( const actionsRef = useRef(null); useEffect(() => { - if (!node?.isFocused) { + const modalOpen = document.querySelector( + '[role="dialog"], .c__modal, [data-modal], .c__modal__overlay, .ReactModal_Content', + ); + if (!node?.isFocused || modalOpen) { return; } const toActions = (e: KeyboardEvent) => { + const modalOpen = document.querySelector( + '[role="dialog"], .c__modal, [data-modal], .c__modal__overlay, .ReactModal_Content', + ); + if (modalOpen) { + return; + } + if (e.key === 'F2' || e.key === 'Enter') { const isAlreadyInActions = actionsRef.current?.contains( document.activeElement, @@ -61,6 +67,13 @@ export const useActionableMode = ( return; } + const modal = document.querySelector( + '[role="dialog"], .c__modal, [data-modal], .c__modal__overlay, .ReactModal_Content', + ); + if (modal) { + return; + } + if (e.key === 'Escape') { e.stopPropagation(); node?.focus?.(); diff --git a/src/frontend/apps/impress/src/components/dropdown-menu/hook/useDropdownFocusManagement.ts b/src/frontend/apps/impress/src/components/dropdown-menu/hook/useDropdownFocusManagement.ts index acc8038d..ec87a497 100644 --- a/src/frontend/apps/impress/src/components/dropdown-menu/hook/useDropdownFocusManagement.ts +++ b/src/frontend/apps/impress/src/components/dropdown-menu/hook/useDropdownFocusManagement.ts @@ -56,7 +56,14 @@ export const useDropdownFocusManagement = ({ } const timer = setTimeout(() => { - // Try to find sub-document by closest ancestor + const modal = document.querySelector( + '[role="dialog"], .c__modal, [data-modal], .c__modal__overlay, .ReactModal_Content', + ); + if (modal) { + return; + } + + // Only handle focus return if no modal is open let subPageItem = actionsRef?.current?.closest('.--docs-sub-page-item'); // If not found, try to find by data-testid diff --git a/src/frontend/apps/impress/src/features/docs/doc-management/components/SimpleDocItem.tsx b/src/frontend/apps/impress/src/features/docs/doc-management/components/SimpleDocItem.tsx index 672947ac..a0423f7f 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-management/components/SimpleDocItem.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-management/components/SimpleDocItem.tsx @@ -49,6 +49,7 @@ export const SimpleDocItem = ({ $overflow="auto" $width="100%" className="--docs--simple-doc-item" + role="presentation" >