mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-21 20:47:08 -05:00
Merge pull request #9955 from overleaf/jel-actions-list
[web] Maintain project list in actions modal while processing and show processed GitOrigin-RevId: c671b97646918e185d7989f539b44562a87873ee
This commit is contained in:
parent
fe963ba692
commit
8323cb37e8
3 changed files with 133 additions and 5 deletions
|
@ -1,4 +1,4 @@
|
|||
import { memo, useEffect, useState } from 'react'
|
||||
import { memo, useEffect, useRef, useState } from 'react'
|
||||
import { Alert, Modal } from 'react-bootstrap'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Project } from '../../../../../../types/project/dashboard/api'
|
||||
|
@ -31,6 +31,8 @@ function ProjectsActionModal({
|
|||
const { t } = useTranslation()
|
||||
const [errors, setErrors] = useState<Array<any>>([])
|
||||
const [isProcessing, setIsProcessing] = useState(false)
|
||||
const [projectsToDisplay, setProjectsToDisplay] = useState<Project[]>([])
|
||||
const projectsRef = useRef<Project[]>([])
|
||||
const isMounted = useIsMounted()
|
||||
|
||||
async function handleActionForProjects(projects: Array<Project>) {
|
||||
|
@ -64,8 +66,21 @@ function ProjectsActionModal({
|
|||
'project action',
|
||||
action
|
||||
)
|
||||
|
||||
if (projectsRef.current.length > 0) {
|
||||
// maintain the original list in the display of the modal,
|
||||
// even after some project actions have completed
|
||||
setProjectsToDisplay(projectsRef.current)
|
||||
} else {
|
||||
projectsRef.current = projects
|
||||
setProjectsToDisplay(projects)
|
||||
}
|
||||
} else {
|
||||
projectsRef.current = []
|
||||
}
|
||||
}, [action, showModal])
|
||||
}, [action, projects, projectsRef, showModal])
|
||||
|
||||
const projectIdsRemainingToProcess = projects.map(p => p.id)
|
||||
|
||||
return (
|
||||
<AccessibleModal
|
||||
|
@ -80,13 +95,21 @@ function ProjectsActionModal({
|
|||
</Modal.Header>
|
||||
<Modal.Body>
|
||||
{bodyTop}
|
||||
<ul>
|
||||
{projects.map(project => (
|
||||
<li key={`projects-action-list-${project.id}`}>
|
||||
<ul className="projects-action-list">
|
||||
{projectsToDisplay.map(project => (
|
||||
<li
|
||||
key={`projects-action-list-${project.id}`}
|
||||
className={
|
||||
projectIdsRemainingToProcess.includes(project.id)
|
||||
? ''
|
||||
: 'completed-project-action'
|
||||
}
|
||||
>
|
||||
<b>{project.name}</b>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
|
||||
{bodyBottom}
|
||||
</Modal.Body>
|
||||
<Modal.Footer>
|
||||
|
|
|
@ -707,3 +707,23 @@
|
|||
flex: @project-list-sidebar-wrapper-flex;
|
||||
}
|
||||
}
|
||||
|
||||
.projects-action-list {
|
||||
list-style: none;
|
||||
|
||||
li:not(.completed-project-action) {
|
||||
list-style-type: disc;
|
||||
}
|
||||
|
||||
li.completed-project-action {
|
||||
&:before {
|
||||
display: inline-block;
|
||||
content: '\f00c';
|
||||
font-family: FontAwesome;
|
||||
color: @ol-green;
|
||||
width: 1.2em;
|
||||
margin-left: -1.2em;
|
||||
height: 0; // prevent slight vertical layout shift
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -640,6 +640,91 @@ describe('<ProjectListRoot />', function () {
|
|||
screen.getByText(copiedProjectName)
|
||||
})
|
||||
})
|
||||
|
||||
describe('projects list in actions modal', function () {
|
||||
let modal: HTMLElement
|
||||
let projectsToProcess: any[]
|
||||
|
||||
function selectedProjectNames() {
|
||||
projectsToProcess = []
|
||||
// needs to be done ahead of opening modal
|
||||
|
||||
allCheckboxes = screen.getAllByRole<HTMLInputElement>('checkbox')
|
||||
// update list so we know which are checked
|
||||
|
||||
const tableRows = screen.getAllByRole('row')
|
||||
|
||||
for (const [index, checkbox] of allCheckboxes.entries()) {
|
||||
if (checkbox.checked) {
|
||||
const linkForProjectToCopy = within(tableRows[index]).getByRole(
|
||||
'link'
|
||||
)
|
||||
const projectNameToCopy = linkForProjectToCopy.textContent
|
||||
projectsToProcess.push(projectNameToCopy)
|
||||
}
|
||||
}
|
||||
|
||||
expect(projectsToProcess.length > 0).to.be.true
|
||||
}
|
||||
|
||||
function selectedMatchesDisplayed(expectedLength: number) {
|
||||
selectedProjectNames()
|
||||
// any action will work for check since they all use the same modal
|
||||
const archiveButton = within(actionsToolbar).getByLabelText('Archive')
|
||||
fireEvent.click(archiveButton)
|
||||
modal = screen.getAllByRole('dialog')[0]
|
||||
|
||||
const listitems = within(modal).getAllByRole('listitem')
|
||||
expect(listitems.length).to.equal(projectsToProcess.length)
|
||||
expect(listitems.length).to.equal(expectedLength)
|
||||
|
||||
for (const projectName of projectsToProcess) {
|
||||
within(modal).getByText(projectName)
|
||||
}
|
||||
}
|
||||
|
||||
beforeEach(function () {
|
||||
allCheckboxes = screen.getAllByRole<HTMLInputElement>('checkbox')
|
||||
// first one is the select all checkbox, just check 2 at first
|
||||
fireEvent.click(allCheckboxes[1])
|
||||
fireEvent.click(allCheckboxes[2])
|
||||
|
||||
actionsToolbar = screen.getAllByRole('toolbar')[0]
|
||||
})
|
||||
|
||||
it('opens the modal with the 2 originally selected projects', function () {
|
||||
selectedMatchesDisplayed(2)
|
||||
})
|
||||
|
||||
it('shows correct list after closing modal, changing selecting, and reopening modal', async function () {
|
||||
selectedMatchesDisplayed(2)
|
||||
|
||||
const cancelButton = screen.getByRole('button', { name: 'Cancel' })
|
||||
fireEvent.click(cancelButton)
|
||||
expect(screen.queryByRole('dialog', { hidden: false })).to.be.null
|
||||
await screen.findAllByRole('checkbox')
|
||||
fireEvent.click(allCheckboxes[3])
|
||||
selectedMatchesDisplayed(3)
|
||||
})
|
||||
|
||||
it('maintains original list even after some have been processed', async function () {
|
||||
const totalProjectsToProcess = 2
|
||||
selectedMatchesDisplayed(totalProjectsToProcess)
|
||||
const button = screen.getByRole('button', { name: 'Confirm' })
|
||||
fireEvent.click(button)
|
||||
project1Id = allCheckboxes[1].getAttribute('data-project-id')
|
||||
fetchMock.post('express:/project/:id/archive', {
|
||||
status: 200,
|
||||
})
|
||||
fetchMock.post(`express:/${project2Id}/:id/archive`, {
|
||||
status: 500,
|
||||
})
|
||||
|
||||
await screen.findByRole('alert') // ensure that error was thrown for the 2nd project
|
||||
const listitems = within(modal).getAllByRole('listitem')
|
||||
expect(listitems.length).to.equal(totalProjectsToProcess)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('search', function () {
|
||||
|
|
Loading…
Reference in a new issue