Просмотр исходного кода

Merge pull request #9676 from overleaf/jel-project-tools-unarchive

[web] Add unarchive button to project tools

GitOrigin-RevId: d1c7c693eb13cb7473b65e311f09ffe7a7f0d88f
Jessica Lawshe 4 лет назад
Родитель
Сommit
30fd0bfc9d

+ 21 - 0
services/web/frontend/js/features/project-list/components/table/project-tools/buttons/unarchive-projects-button.tsx

@@ -0,0 +1,21 @@
+import { memo } from 'react'
+import { Button } from 'react-bootstrap'
+import { useTranslation } from 'react-i18next'
+import { useProjectListContext } from '../../../../context/project-list-context'
+import { unarchiveProject } from '../../../../util/api'
+
+function UnarchiveProjectsButton() {
+  const { selectedProjects, updateProjectViewData } = useProjectListContext()
+  const { t } = useTranslation()
+
+  const handleUnarchiveProjects = async () => {
+    for (const project of selectedProjects) {
+      await unarchiveProject(project.id)
+      updateProjectViewData({ ...project, archived: false, selected: false })
+    }
+  }
+
+  return <Button onClick={handleUnarchiveProjects}>{t('unarchive')}</Button>
+}
+
+export default memo(UnarchiveProjectsButton)

+ 4 - 4
services/web/frontend/js/features/project-list/components/table/project-tools/buttons/untrash-projects-button.tsx

@@ -1,4 +1,4 @@
-import { memo, useCallback } from 'react'
+import { memo } from 'react'
 import { Button } from 'react-bootstrap'
 import { useTranslation } from 'react-i18next'
 import { useProjectListContext } from '../../../../context/project-list-context'
@@ -8,12 +8,12 @@ function UntrashProjectsButton() {
   const { selectedProjects, updateProjectViewData } = useProjectListContext()
   const { t } = useTranslation()
 
-  const handleUntrashProjects = useCallback(async () => {
-    for (const [, project] of Object.entries(selectedProjects)) {
+  const handleUntrashProjects = async () => {
+    for (const project of selectedProjects) {
       await untrashProject(project.id)
       updateProjectViewData({ ...project, trashed: false, selected: false })
     }
-  }, [selectedProjects, updateProjectViewData])
+  }
 
   return <Button onClick={handleUntrashProjects}>{t('untrash')}</Button>
 }

+ 2 - 0
services/web/frontend/js/features/project-list/components/table/project-tools/project-tools.tsx

@@ -4,6 +4,7 @@ import { useProjectListContext } from '../../../context/project-list-context'
 import ArchiveProjectsButton from './buttons/archive-projects-button'
 import DownloadProjectsButton from './buttons/download-projects-button'
 import TrashProjectsButton from './buttons/trash-projects-button'
+import UnarchiveProjectsButton from './buttons/unarchive-projects-button'
 import UntrashProjectsButton from './buttons/untrash-projects-button'
 
 function ProjectTools() {
@@ -18,6 +19,7 @@ function ProjectTools() {
 
       <ButtonGroup>
         {filter === 'trashed' && <UntrashProjectsButton />}
+        {filter === 'archived' && <UnarchiveProjectsButton />}
       </ButtonGroup>
     </ButtonToolbar>
   )

+ 38 - 1
services/web/test/frontend/features/project-list/components/project-list-root.test.tsx

@@ -4,7 +4,11 @@ import fetchMock from 'fetch-mock'
 import sinon from 'sinon'
 import ProjectListRoot from '../../../../../frontend/js/features/project-list/components/project-list-root'
 import { renderWithProjectListContext } from '../helpers/render-with-context'
-import { owner, makeLongProjectList } from '../fixtures/projects-data'
+import {
+  owner,
+  archivedProjects,
+  makeLongProjectList,
+} from '../fixtures/projects-data'
 const { fullList, currentList, trashedList } = makeLongProjectList(40)
 
 const userId = owner.id
@@ -191,10 +195,43 @@ describe('<ProjectListRoot />', function () {
         // first one is the select all checkbox
         fireEvent.click(allCheckboxes[1])
         project1Id = allCheckboxes[1].getAttribute('data-project-id')
+
+        actionsToolbar = screen.getAllByRole('toolbar')[0]
       })
+
       it('does not show the archive button in toolbar when archive view selected', function () {
         expect(screen.queryByLabelText('Archive')).to.be.null
       })
+
+      it('restores all projects when selected', async function () {
+        fetchMock.delete(`express:/project/:id/archive`, {
+          status: 200,
+        })
+
+        const unarchiveButton =
+          within(actionsToolbar).getByText<HTMLInputElement>('Restore')
+        fireEvent.click(unarchiveButton)
+
+        await fetchMock.flush(true)
+        expect(fetchMock.done()).to.be.true
+
+        screen.getByText('No projects')
+      })
+
+      it('only unarchive the selected projects', async function () {
+        // beforeEach selected all, so uncheck the 1st project
+        fireEvent.click(allCheckboxes[1])
+
+        const allCheckboxesChecked = allCheckboxes.filter(c => c.checked)
+        expect(allCheckboxesChecked.length).to.equal(
+          archivedProjects.length - 1
+        )
+
+        await fetchMock.flush(true)
+        expect(fetchMock.done()).to.be.true
+
+        expect(screen.queryByText('No projects')).to.be.null
+      })
     })
 
     describe('trashed projects', function () {

+ 4 - 4
services/web/test/frontend/features/project-list/fixtures/projects-data.ts

@@ -132,13 +132,13 @@ export const projectsData: Array<Project> = [
   trashedAndNotOwnedProject,
 ]
 
-export const currentProjects: Array<Project> = projectsData.filter(
+export const archivedProjects = projectsData.filter(({ archived }) => archived)
+
+export const currentProjects = projectsData.filter(
   ({ archived, trashed }) => !archived && !trashed
 )
 
-export const trashedProjects: Array<Project> = projectsData.filter(
-  ({ trashed }) => trashed
-)
+export const trashedProjects = projectsData.filter(({ trashed }) => trashed)
 
 export const makeLongProjectList = (listLength: number) => {
   const longList = [...projectsData]