diff --git a/services/web/frontend/js/features/file-tree/components/file-tree-item/file-tree-item-menu.js b/services/web/frontend/js/features/file-tree/components/file-tree-item/file-tree-item-menu.js index a1f6d45c6b..33dab9a510 100644 --- a/services/web/frontend/js/features/file-tree/components/file-tree-item/file-tree-item-menu.js +++ b/services/web/frontend/js/features/file-tree/components/file-tree-item/file-tree-item-menu.js @@ -1,9 +1,10 @@ import React, { useState } from 'react' +import { findDOMNode } from 'react-dom' import PropTypes from 'prop-types' import { useTranslation } from 'react-i18next' import withoutPropagation from '../../../../infrastructure/without-propagation' -import { Dropdown } from 'react-bootstrap' +import { Dropdown, Overlay } from 'react-bootstrap' import Icon from '../../../../shared/components/icon' import FileTreeItemMenuItems from './file-tree-item-menu-items' @@ -12,6 +13,7 @@ function FileTreeItemMenu({ id }) { const { t } = useTranslation() const [dropdownOpen, setDropdownOpen] = useState(false) + const [dropdownTarget, setDropdownTarget] = useState() function handleToggle(wantOpen) { setDropdownOpen(wantOpen) @@ -21,6 +23,13 @@ function FileTreeItemMenu({ id }) { handleToggle(false) } + const toggleRef = component => { + if (component) { + // eslint-disable-next-line react/no-find-dom-node + setDropdownTarget(findDOMNode(component)) + } + } + return ( - - - + + + ) } @@ -47,4 +62,20 @@ FileTreeItemMenu.propTypes = { id: PropTypes.string.isRequired, } +function Menu({ dropdownId, style, className }) { + return ( +
+
    + +
+
+ ) +} + +Menu.propTypes = { + dropdownId: PropTypes.string.isRequired, + style: PropTypes.object, + className: PropTypes.string, +} + export default FileTreeItemMenu diff --git a/services/web/test/frontend/features/file-tree/components/file-tree-doc.test.js b/services/web/test/frontend/features/file-tree/components/file-tree-doc.test.js index 8909ac88be..80b8efa51f 100644 --- a/services/web/test/frontend/features/file-tree/components/file-tree-doc.test.js +++ b/services/web/test/frontend/features/file-tree/components/file-tree-doc.test.js @@ -35,11 +35,6 @@ describe('', function () { fireEvent.click(treeitem) screen.getByRole('treeitem', { selected: true }) - screen.getByRole('menuitem', { name: 'Rename' }) - screen.getByRole('menuitem', { name: 'Delete' }) - screen.getByRole('menuitem', { name: 'New File' }) - screen.getByRole('menuitem', { name: 'New Folder' }) - screen.getByRole('menuitem', { name: 'Upload' }) }) it('renders as linked file', function () { diff --git a/services/web/test/frontend/features/file-tree/components/file-tree-folder.test.js b/services/web/test/frontend/features/file-tree/components/file-tree-folder.test.js index 416bb4e921..c9a94fbef3 100644 --- a/services/web/test/frontend/features/file-tree/components/file-tree-folder.test.js +++ b/services/web/test/frontend/features/file-tree/components/file-tree-folder.test.js @@ -50,13 +50,6 @@ describe('', function () { const treeitem = screen.getByRole('treeitem', { selected: false }) fireEvent.click(treeitem) - screen.getByRole('treeitem', { selected: true }) - screen.getByRole('menuitem', { name: 'Rename' }) - screen.getByRole('menuitem', { name: 'Delete' }) - screen.getByRole('menuitem', { name: 'New File' }) - screen.getByRole('menuitem', { name: 'New Folder' }) - screen.getByRole('menuitem', { name: 'Upload' }) - screen.getByRole('treeitem', { selected: true }) expect(screen.queryByRole('tree')).to.not.exist }) diff --git a/services/web/test/frontend/features/file-tree/components/file-tree-item/file-tree-item-inner.test.js b/services/web/test/frontend/features/file-tree/components/file-tree-item/file-tree-item-inner.test.js index d4aea62ad1..289cba4850 100644 --- a/services/web/test/frontend/features/file-tree/components/file-tree-item/file-tree-item-inner.test.js +++ b/services/web/test/frontend/features/file-tree/components/file-tree-item/file-tree-item-inner.test.js @@ -5,6 +5,7 @@ import { screen, fireEvent } from '@testing-library/react' import renderWithContext from '../../helpers/render-with-context' import FileTreeitemInner from '../../../../../../frontend/js/features/file-tree/components/file-tree-item/file-tree-item-inner' +import FileTreeContextMenu from '../../../../../../frontend/js/features/file-tree/components/file-tree-context-menu' describe('', function () { const setContextMenuCoords = sinon.stub() @@ -41,18 +42,22 @@ describe('', function () { it('open / close', function () { const { container } = renderWithContext( - + <> + + + ) + expect(screen.queryByRole('menu')).to.be.null + + // open the context menu const entityElement = container.querySelector('div.entity') - - screen.getByRole('menu', { visible: false }) - fireEvent.contextMenu(entityElement) screen.getByRole('menu', { visible: true }) - fireEvent.contextMenu(entityElement) - screen.getByRole('menu', { visible: false }) + // close the context menu + fireEvent.click(entityElement) + expect(screen.queryByRole('menu')).to.be.null }) }) @@ -83,7 +88,8 @@ describe('', function () { }, } ) - + const toggleButton = screen.getByRole('button', { name: 'Menu' }) + fireEvent.click(toggleButton) const renameButton = screen.getByRole('menuitem', { name: 'Rename' }) fireEvent.click(renameButton) expect(screen.queryByRole('button', { name: 'bar.tex' })).to.not.exist diff --git a/services/web/test/frontend/features/file-tree/components/file-tree-item/file-tree-item-menu.test.js b/services/web/test/frontend/features/file-tree/components/file-tree-item/file-tree-item-menu.test.js index 76caaa9be9..d92918d630 100644 --- a/services/web/test/frontend/features/file-tree/components/file-tree-item/file-tree-item-menu.test.js +++ b/services/web/test/frontend/features/file-tree/components/file-tree-item/file-tree-item-menu.test.js @@ -1,5 +1,6 @@ import React from 'react' import sinon from 'sinon' +import { expect } from 'chai' import { screen, fireEvent } from '@testing-library/react' import renderWithContext from '../../helpers/render-with-context' @@ -20,7 +21,9 @@ describe('', function () { /> ) - screen.getByRole('button', { name: 'Menu' }) + const toggleButton = screen.getByRole('button', { name: 'Menu' }) + fireEvent.click(toggleButton) + screen.getByRole('menu') }) @@ -32,9 +35,9 @@ describe('', function () { /> ) - const toggleButton = screen.getByRole('button', { name: 'Menu' }) + expect(screen.queryByRole('menu')).to.be.null - screen.getByRole('menu', { visible: false }) + const toggleButton = screen.getByRole('button', { name: 'Menu' }) fireEvent.click(toggleButton) screen.getByRole('menu', { visible: true }) diff --git a/services/web/test/frontend/features/file-tree/components/file-tree-root.test.js b/services/web/test/frontend/features/file-tree/components/file-tree-root.test.js index 727154e290..70dda86a79 100644 --- a/services/web/test/frontend/features/file-tree/components/file-tree-root.test.js +++ b/services/web/test/frontend/features/file-tree/components/file-tree-root.test.js @@ -89,6 +89,8 @@ describe('', function () { // selected) This is needed to make sure the test fail. const treeitemFile = screen.getByRole('treeitem', { name: 'main.tex' }) fireEvent.click(treeitemFile, { ctrlKey: true }) + const toggleButton = screen.getByRole('button', { name: 'Menu' }) + fireEvent.click(toggleButton) const deleteButton = screen.getByRole('menuitem', { name: 'Delete' }) fireEvent.click(deleteButton) await waitFor(() => screen.getByRole('button', { name: 'Cancel' })) diff --git a/services/web/test/frontend/features/file-tree/flows/context-menu.test.js b/services/web/test/frontend/features/file-tree/flows/context-menu.test.js index 16d151b567..0b8dddaf5e 100644 --- a/services/web/test/frontend/features/file-tree/flows/context-menu.test.js +++ b/services/web/test/frontend/features/file-tree/flows/context-menu.test.js @@ -36,11 +36,11 @@ describe('FileTree Context Menu Flow', function () { ) const treeitem = screen.getByRole('button', { name: 'main.tex' }) - expect(screen.getAllByRole('menu').length).to.equal(1) // toolbar + expect(screen.queryByRole('menu')).to.be.null fireEvent.contextMenu(treeitem) - expect(screen.getAllByRole('menu').length).to.equal(2) // toolbar + menu + screen.getByRole('menu') }) it("doesn't open in read only mode", async function () { diff --git a/services/web/test/frontend/features/file-tree/flows/delete-entity.test.js b/services/web/test/frontend/features/file-tree/flows/delete-entity.test.js index c7d5218a90..7e3ec37cda 100644 --- a/services/web/test/frontend/features/file-tree/flows/delete-entity.test.js +++ b/services/web/test/frontend/features/file-tree/flows/delete-entity.test.js @@ -53,6 +53,9 @@ describe('FileTree Delete Entity Flow', function () { const treeitem = screen.getByRole('treeitem', { name: 'main.tex' }) fireEvent.click(treeitem) + const toggleButton = screen.getByRole('button', { name: 'Menu' }) + fireEvent.click(toggleButton) + const deleteButton = screen.getByRole('menuitem', { name: 'Delete' }) fireEvent.click(deleteButton) }) @@ -186,6 +189,8 @@ describe('FileTree Delete Entity Flow', function () { // as a proxy to check that the child entity has been unselect we start // a delete and ensure the modal is displayed (the cancel button can be // selected) This is needed to make sure the test fail. + const toggleButton = screen.getByRole('button', { name: 'Menu' }) + fireEvent.click(toggleButton) const deleteButton = screen.getByRole('menuitem', { name: 'Delete' }) fireEvent.click(deleteButton) await waitFor(() => screen.getByRole('button', { name: 'Cancel' })) @@ -193,7 +198,7 @@ describe('FileTree Delete Entity Flow', function () { }) describe('multiple entities', function () { - beforeEach(function () { + beforeEach(async function () { const rootFolder = [ { _id: 'root-folder-id', @@ -202,6 +207,7 @@ describe('FileTree Delete Entity Flow', function () { fileRefs: [{ _id: '789ghi', name: 'my.bib' }], }, ] + render( ) + // select two files const treeitemDoc = screen.getByRole('treeitem', { name: 'main.tex' }) fireEvent.click(treeitemDoc) const treeitemFile = screen.getByRole('treeitem', { name: 'my.bib' }) fireEvent.click(treeitemFile, { ctrlKey: true }) - const deleteButton = screen.getAllByRole('menuitem', { - name: 'Delete', - })[0] + // open the context menu + const treeitemButton = screen.getByRole('button', { name: 'my.bib' }) + fireEvent.contextMenu(treeitemButton) + + // make sure the menu has opened, with only a "Delete" item (as multiple files are selected) + screen.getByRole('menu') + const menuItems = await screen.findAllByRole('menuitem') + expect(menuItems.length).to.equal(1) + + // select the Delete menu item + const deleteButton = screen.getByRole('menuitem', { name: 'Delete' }) fireEvent.click(deleteButton) }) diff --git a/services/web/test/frontend/features/file-tree/flows/rename-entity.test.js b/services/web/test/frontend/features/file-tree/flows/rename-entity.test.js index f83999c345..ba55d549b1 100644 --- a/services/web/test/frontend/features/file-tree/flows/rename-entity.test.js +++ b/services/web/test/frontend/features/file-tree/flows/rename-entity.test.js @@ -185,6 +185,9 @@ describe('FileTree Rename Entity Flow', function () { const treeitem = screen.getByRole('treeitem', { name: treeitemName }) fireEvent.click(treeitem) + const toggleButton = screen.getByRole('button', { name: 'Menu' }) + fireEvent.click(toggleButton) + const renameButton = screen.getByRole('menuitem', { name: 'Rename' }) fireEvent.click(renameButton)