diff --git a/services/web/frontend/js/features/project-list/components/dropdown/actions-dropdown.tsx b/services/web/frontend/js/features/project-list/components/dropdown/actions-dropdown.tsx index b4f6055685..5b11604b26 100644 --- a/services/web/frontend/js/features/project-list/components/dropdown/actions-dropdown.tsx +++ b/services/web/frontend/js/features/project-list/components/dropdown/actions-dropdown.tsx @@ -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 ( + + {(text, handleOpenModal) => ( + handleClick(handleOpenModal)} + className="projects-action-menu-item" + > + {' '} + {text} + + )} + + ) +} + type ActionDropdownProps = { project: Project } @@ -216,6 +237,7 @@ function ActionsDropdown({ project }: ActionDropdownProps) { + 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)} + + + ) +} + +export default memo(RenameProjectButton) diff --git a/services/web/frontend/js/features/project-list/components/table/project-tools/buttons/project-tools-more-dropdown-button.tsx b/services/web/frontend/js/features/project-list/components/table/project-tools/buttons/project-tools-more-dropdown-button.tsx index d0da91f076..2c059f437f 100644 --- a/services/web/frontend/js/features/project-list/components/table/project-tools/buttons/project-tools-more-dropdown-button.tsx +++ b/services/web/frontend/js/features/project-list/components/table/project-tools/buttons/project-tools-more-dropdown-button.tsx @@ -12,7 +12,10 @@ function ProjectToolsMoreDropdownButton() { {t('more')} - + diff --git a/services/web/test/frontend/features/project-list/components/table/cells/action-buttons/rename-project-button.test.tsx b/services/web/test/frontend/features/project-list/components/table/cells/action-buttons/rename-project-button.test.tsx new file mode 100644 index 0000000000..bf7773b56c --- /dev/null +++ b/services/web/test/frontend/features/project-list/components/table/cells/action-buttons/rename-project-button.test.tsx @@ -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( + + {(text, onClick) => { + return + }} + + ) +} + +describe('', 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 + ) + }) +}) diff --git a/services/web/test/frontend/features/project-list/components/table/project-tools/project-tools-rename.test.tsx b/services/web/test/frontend/features/project-list/components/table/project-tools/project-tools-rename.test.tsx index 420cd10f8b..276c98fdee 100644 --- a/services/web/test/frontend/features/project-list/components/table/project-tools/project-tools-rename.test.tsx +++ b/services/web/test/frontend/features/project-list/components/table/project-tools/project-tools-rename.test.tsx @@ -64,14 +64,20 @@ describe('', function () { render() 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() 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') }) }) diff --git a/services/web/test/frontend/features/project-list/fixtures/projects-data.ts b/services/web/test/frontend/features/project-list/fixtures/projects-data.ts index cafc192d63..dd294c8a58 100644 --- a/services/web/test/frontend/features/project-list/fixtures/projects-data.ts +++ b/services/web/test/frontend/features/project-list/fixtures/projects-data.ts @@ -97,6 +97,8 @@ export const trashedAndNotOwnedProject = { export const sharedProject = archiveableProject +export const ownedProject = copyableProject + export const projectsData: Array = [ copyableProject, archiveableProject,