diff --git a/CHANGELOG.md b/CHANGELOG.md index 8949108c6..aefb509a1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ and this project adheres to ### Added +- ✨(frontend) leave a document #2410 - ✨(frontend) add top parent on sub docs search #1952 - ✨(frontend) unauthenticated users can search #2407 diff --git a/src/frontend/apps/e2e/__tests__/app-impress/doc-grid.spec.ts b/src/frontend/apps/e2e/__tests__/app-impress/doc-grid.spec.ts index 02605f7bd..3cef7cd75 100644 --- a/src/frontend/apps/e2e/__tests__/app-impress/doc-grid.spec.ts +++ b/src/frontend/apps/e2e/__tests__/app-impress/doc-grid.spec.ts @@ -4,6 +4,7 @@ import { clickInEditorShareButton, createDoc, getGridRow, + getOtherBrowserName, verifyDocName, } from './utils-common'; import { addNewMember, connectOtherUserToDoc } from './utils-share'; @@ -166,50 +167,85 @@ test.describe('Document grid item options', () => { await expect(page.getByText(docTitle)).toBeHidden(); }); - test("it checks if the delete option is disabled if we don't have the destroy capability", async ({ - page, - }) => { - await page.route(/.*\/api\/v1.0\/documents\/\?page=1/, async (route) => { - await route.fulfill({ - json: { - results: [ - { - id: 'mocked-document-id', - content: '', - title: 'Mocked document', - accesses: [], - abilities: { - destroy: false, // Means not owner - link_configuration: false, - versions_destroy: false, - versions_list: true, - versions_retrieve: true, - accesses_manage: false, // Means not admin - update: false, - partial_update: false, // Means not editor - retrieve: true, - }, - link_reach: 'restricted', - created_at: '2021-09-01T09:00:00Z', - user_roles: ['editor'], - user_role: 'editor', - }, - ], - }, - }); - }); + test('it checks the leave feature', async ({ page, browserName }) => { + const [docTitle] = await createDoc(page, `leave doc`, browserName); + await page.goto('/'); - const button = page - .getByTestId(`docs-grid-actions-button-mocked-document-id`) - .first(); - await expect(button).toBeVisible(); - await button.click(); - const removeButton = page - .getByTestId(`docs-grid-actions-remove-mocked-document-id`) - .first(); - await expect(removeButton).toBeVisible(); - await removeButton.isDisabled(); + // Assert the document is visible in the grid results + await expect( + page.getByLabel('Documents grid').getByText(docTitle), + ).toBeVisible(); + + const row = await getGridRow(page, docTitle); + await row.getByRole('button', { name: /Open the menu of actions/ }).click(); + + await page.getByRole('menuitem', { name: 'Leave' }).click(); + + const modal = page.getByRole('dialog', { + name: 'Confirmation to leave the document', + }); + + await expect( + modal.getByRole('heading', { name: 'Leave a doc' }), + ).toBeVisible(); + + // Check the message when the user is the unique owner + await expect( + modal.getByText( + 'You cannot leave this document because you are the unique owner.', + ), + ).toBeVisible(); + + await expect( + modal.getByRole('button', { + name: 'Confirm leaving the document', + }), + ).toBeHidden(); + + await modal.getByRole('button', { name: 'Close the leave modal' }).click(); + + // Assert the document is visible in the search results + await page.getByRole('button', { name: 'Search docs' }).click(); + await page.getByPlaceholder('Type the name of a document').fill(docTitle); + await page.getByRole('option').getByText(docTitle).click(); + + // We share the doc with another user with owner role + const otherBrowserName = getOtherBrowserName(browserName); + await page.getByRole('button', { name: 'Share' }).click(); + await addNewMember(page, 0, 'Owner', otherBrowserName); + await expect( + page + .getByRole('listbox', { name: 'Suggestions' }) + .getByText(new RegExp(otherBrowserName)), + ).toBeVisible(); + await page.getByRole('button', { name: 'Close the share modal' }).click(); + + // Leave the document + await page + .getByRole('button', { name: /Open the document options/ }) + .click(); + await page.getByRole('menuitem', { name: 'Leave' }).click(); + // Check the message + await expect( + modal.getByText( + 'This document and all the sub-documents will no longer be visible', + ), + ).toBeVisible(); + await page + .getByRole('button', { name: 'Confirm leaving the document' }) + .click(); + + // We are on the grid page and the document is not visible in the grid results + await expect(page.getByLabel('Documents grid')).toBeVisible(); + await expect( + page.getByLabel('Documents grid').getByText(docTitle), + ).toBeHidden(); + + // We search the document and it's not visible in the search results either + await page.getByRole('button', { name: 'Search docs' }).click(); + await page.getByPlaceholder('Type the name of a document').fill(docTitle); + await expect(page.getByRole('option').getByText(docTitle)).toBeHidden(); }); }); diff --git a/src/frontend/apps/impress/src/assets/icons/ui-kit/leave.svg b/src/frontend/apps/impress/src/assets/icons/ui-kit/leave.svg new file mode 100644 index 000000000..1f3b7860b --- /dev/null +++ b/src/frontend/apps/impress/src/assets/icons/ui-kit/leave.svg @@ -0,0 +1,10 @@ + + + + diff --git a/src/frontend/apps/impress/src/features/docs/doc-header/components/DocToolBox.tsx b/src/frontend/apps/impress/src/features/docs/doc-header/components/DocToolBox.tsx index 9dc412e17..d052dcb10 100644 --- a/src/frontend/apps/impress/src/features/docs/doc-header/components/DocToolBox.tsx +++ b/src/frontend/apps/impress/src/features/docs/doc-header/components/DocToolBox.tsx @@ -1,4 +1,4 @@ -import { Button, useModal } from '@gouvfr-lasuite/cunningham-react'; +import { Button } from '@gouvfr-lasuite/cunningham-react'; import { DropdownMenu, DropdownMenuItem, @@ -18,6 +18,7 @@ import SharedSVG from '@/assets/icons/ui-kit/group.svg'; import HistorySVG from '@/assets/icons/ui-kit/history.svg'; import KeepSVG from '@/assets/icons/ui-kit/keep.svg'; import KeepOffSVG from '@/assets/icons/ui-kit/keep_off.svg'; +import LeaveSVG from '@/assets/icons/ui-kit/leave.svg'; import MarkdownCopySVG from '@/assets/icons/ui-kit/markdown_copy.svg'; import MoreSVG from '@/assets/icons/ui-kit/more_horiz.svg'; import { @@ -60,6 +61,16 @@ const DocShareModal = dynamic( { ssr: false }, ); +const ConfirmationLeaveModal = dynamic( + () => + import('@/docs/doc-share/components/ConfirmationLeaveModal').then( + (mod) => ({ + default: mod.ConfirmationLeaveModal, + }), + ), + { ssr: false }, +); + const ModalExport = process.env.NEXT_PUBLIC_PUBLISH_AS_MIT === 'false' ? dynamic( @@ -88,16 +99,18 @@ export const DocToolBox = ({ doc }: DocToolBoxProps) => { const treeContext = useTreeContext(); const router = useRouter(); const { isTopRoot } = useDocUtils(doc); + const isTopParent = doc.id === treeContext?.root?.id; // it can be a child but not for the current user const { authenticated } = useAuth(); const copyCurrentEditorToClipboard = useCopyCurrentEditorToClipboard(); const [openDropdown, setOpenDropdown] = useState(false); const [isModalRemoveOpen, setIsModalRemoveOpen] = useState(false); const [isModalExportOpen, setIsModalExportOpen] = useState(false); - const shareModal = useModal(); + const [isModalShareOpen, setIsModalShareOpen] = useState(false); + const [isModalHistoryOpen, setIsModalHistoryOpen] = useState(false); + const [isModalLeaveOpen, setIsModalLeaveOpen] = useState(false); const [isPresenterOpen, setIsPresenterOpen] = useState(false); - const selectHistoryModal = useModal(); - const { restoreFocus } = useFocusStore(); + const { restoreFocus, addLastFocus } = useFocusStore(); const { isMobile } = useResponsiveStore(); const copyDocLink = useCopyDocLink(doc.id); const { mutate: duplicateDoc } = useDuplicateDoc({ @@ -151,7 +164,7 @@ export const DocToolBox = ({ doc }: DocToolBoxProps) => { label: t('Share'), icon: