mirror of
https://github.com/overleaf/overleaf.git
synced 2025-04-11 00:27:34 +00:00
Merge pull request #18981 from overleaf/mj-mobile-dropdown
[web] Add rename button to project list mobile view GitOrigin-RevId: 4ca3c68dcaf4e0d5e97f501084b9f850f9a4e867
This commit is contained in:
parent
7b47acc486
commit
46d687855e
6 changed files with 147 additions and 3 deletions
|
@ -12,6 +12,7 @@ import LeaveProjectButton from '../table/cells/action-buttons/leave-project-butt
|
|||
import DeleteProjectButton from '../table/cells/action-buttons/delete-project-button'
|
||||
import { Project } from '../../../../../../types/project/dashboard/api'
|
||||
import CompileAndDownloadProjectPDFButton from '../table/cells/action-buttons/compile-and-download-project-pdf-button'
|
||||
import RenameProjectButton from '../table/cells/action-buttons/rename-project-button'
|
||||
|
||||
type ActionButtonProps = {
|
||||
project: Project
|
||||
|
@ -194,6 +195,26 @@ function DeleteProjectButtonMenuItem({ project, onClick }: ActionButtonProps) {
|
|||
)
|
||||
}
|
||||
|
||||
function RenameProjectButtonMenuItem({ project, onClick }: ActionButtonProps) {
|
||||
const handleClick = (handleOpenModal: () => void) => {
|
||||
handleOpenModal()
|
||||
onClick()
|
||||
}
|
||||
return (
|
||||
<RenameProjectButton project={project}>
|
||||
{(text, handleOpenModal) => (
|
||||
<MenuItemButton
|
||||
onClick={() => handleClick(handleOpenModal)}
|
||||
className="projects-action-menu-item"
|
||||
>
|
||||
<Icon type="pencil" className="menu-item-button-icon" />{' '}
|
||||
<span className="menu-item-button-text">{text}</span>
|
||||
</MenuItemButton>
|
||||
)}
|
||||
</RenameProjectButton>
|
||||
)
|
||||
}
|
||||
|
||||
type ActionDropdownProps = {
|
||||
project: Project
|
||||
}
|
||||
|
@ -216,6 +237,7 @@ function ActionsDropdown({ project }: ActionDropdownProps) {
|
|||
<Icon type="ellipsis-h" fw />
|
||||
</Dropdown.Toggle>
|
||||
<Dropdown.Menu className="projects-dropdown-menu text-left">
|
||||
<RenameProjectButtonMenuItem project={project} onClick={handleClose} />
|
||||
<CopyProjectButtonMenuItem project={project} onClick={handleClose} />
|
||||
<DownloadProjectButtonMenuItem
|
||||
project={project}
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
import { Project } from '../../../../../../../../types/project/dashboard/api'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { memo, useCallback, useState } from 'react'
|
||||
import useIsMounted from '@/shared/hooks/use-is-mounted'
|
||||
import RenameProjectModal from '../../../modals/rename-project-modal'
|
||||
|
||||
type RenameProjectButtonProps = {
|
||||
project: Project
|
||||
children: (text: string, handleOpenModal: () => void) => React.ReactElement
|
||||
}
|
||||
|
||||
function RenameProjectButton({ project, children }: RenameProjectButtonProps) {
|
||||
const { t } = useTranslation()
|
||||
const text = t('rename')
|
||||
const [showModal, setShowModal] = useState(false)
|
||||
const isMounted = useIsMounted()
|
||||
|
||||
const handleOpenModal = useCallback(() => {
|
||||
setShowModal(true)
|
||||
}, [])
|
||||
|
||||
const handleCloseModal = useCallback(() => {
|
||||
if (isMounted.current) {
|
||||
setShowModal(false)
|
||||
}
|
||||
}, [isMounted])
|
||||
|
||||
if (project.accessLevel !== 'owner') {
|
||||
return null
|
||||
}
|
||||
return (
|
||||
<>
|
||||
{children(text, handleOpenModal)}
|
||||
<RenameProjectModal
|
||||
handleCloseModal={handleCloseModal}
|
||||
project={project}
|
||||
showModal={showModal}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default memo(RenameProjectButton)
|
|
@ -12,7 +12,10 @@ function ProjectToolsMoreDropdownButton() {
|
|||
<Dropdown.Toggle bsStyle={null} className="btn-secondary">
|
||||
{t('more')}
|
||||
</Dropdown.Toggle>
|
||||
<Dropdown.Menu className="dropdown-menu-right">
|
||||
<Dropdown.Menu
|
||||
className="dropdown-menu-right"
|
||||
data-testid="project-tools-more-dropdown-menu"
|
||||
>
|
||||
<RenameProjectMenuItem />
|
||||
<CopyProjectMenuItem />
|
||||
</Dropdown.Menu>
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
import RenameProjectButton from '@/features/project-list/components/table/cells/action-buttons/rename-project-button'
|
||||
import {
|
||||
renderWithProjectListContext,
|
||||
resetProjectListContextFetch,
|
||||
} from '../../../../helpers/render-with-context'
|
||||
import { ownedProject, sharedProject } from '../../../../fixtures/projects-data'
|
||||
import { Project } from '../../../../../../../../types/project/dashboard/api'
|
||||
import { fireEvent, screen, waitFor } from '@testing-library/react'
|
||||
import fetchMock from 'fetch-mock'
|
||||
import { expect } from 'chai'
|
||||
|
||||
// Little test jig for rendering the button
|
||||
function renderWithProject(project: Project) {
|
||||
renderWithProjectListContext(
|
||||
<RenameProjectButton project={project}>
|
||||
{(text, onClick) => {
|
||||
return <button onClick={onClick}>Rename Project Button</button>
|
||||
}}
|
||||
</RenameProjectButton>
|
||||
)
|
||||
}
|
||||
|
||||
describe('<RenameProjectButton />', function () {
|
||||
afterEach(function () {
|
||||
resetProjectListContextFetch()
|
||||
})
|
||||
|
||||
it('opens the modal when clicked', function () {
|
||||
renderWithProject(ownedProject)
|
||||
const btn = screen.getByRole('button')
|
||||
fireEvent.click(btn)
|
||||
screen.getByText('Rename Project')
|
||||
screen.getByDisplayValue(ownedProject.name)
|
||||
})
|
||||
|
||||
it('does not render the button when already archived', function () {
|
||||
renderWithProject(sharedProject)
|
||||
expect(screen.queryByRole('button')).to.be.null
|
||||
})
|
||||
|
||||
it('should rename the project', async function () {
|
||||
const project = Object.assign({}, ownedProject)
|
||||
const renameProjectMock = fetchMock.post(
|
||||
`express:/project/:projectId/rename`,
|
||||
{
|
||||
status: 200,
|
||||
},
|
||||
{ delay: 0 }
|
||||
)
|
||||
renderWithProject(ownedProject)
|
||||
const btn = screen.getByRole('button')
|
||||
fireEvent.click(btn)
|
||||
screen.getByText('Rename Project')
|
||||
const confirmBtn = screen.getByText('Rename') as HTMLButtonElement
|
||||
expect(confirmBtn.disabled).to.be.true
|
||||
const nameInput = screen.getByDisplayValue(ownedProject.name)
|
||||
fireEvent.change(nameInput, { target: { value: 'new name' } })
|
||||
expect(confirmBtn.disabled).to.be.false
|
||||
fireEvent.click(confirmBtn)
|
||||
expect(confirmBtn.disabled).to.be.true
|
||||
|
||||
await waitFor(
|
||||
() =>
|
||||
expect(renameProjectMock.called(`/project/${project.id}/rename`)).to.be
|
||||
.true
|
||||
)
|
||||
})
|
||||
})
|
|
@ -64,14 +64,20 @@ describe('<ProjectTools />', function () {
|
|||
render(<ProjectListRootInner />)
|
||||
screen.getByLabelText('Select Starfleet Report (readAndWrite)').click()
|
||||
screen.getByRole('button', { name: 'More' }).click()
|
||||
expect(screen.queryByRole('menuitem', { name: 'Rename' })).to.be.null
|
||||
expect(
|
||||
within(
|
||||
screen.getByTestId('project-tools-more-dropdown-menu')
|
||||
).queryByRole('menuitem', { name: 'Rename' })
|
||||
).to.be.null
|
||||
})
|
||||
|
||||
it('displays the Rename option for a project owned by the current user', function () {
|
||||
render(<ProjectListRootInner />)
|
||||
screen.getByLabelText('Select Starfleet Report (owner)').click()
|
||||
screen.getByRole('button', { name: 'More' }).click()
|
||||
screen.getByRole('menuitem', { name: 'Rename' }).click()
|
||||
within(screen.getByTestId('project-tools-more-dropdown-menu'))
|
||||
.getByRole('menuitem', { name: 'Rename' })
|
||||
.click()
|
||||
within(screen.getByRole('dialog')).getByText('Rename Project')
|
||||
})
|
||||
})
|
||||
|
|
|
@ -97,6 +97,8 @@ export const trashedAndNotOwnedProject = <Project>{
|
|||
|
||||
export const sharedProject = archiveableProject
|
||||
|
||||
export const ownedProject = copyableProject
|
||||
|
||||
export const projectsData: Array<Project> = [
|
||||
copyableProject,
|
||||
archiveableProject,
|
||||
|
|
Loading…
Add table
Reference in a new issue