mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-06-02 13:49:00 +02:00
Merge pull request #9794 from overleaf/ab-endpoint-add-remove-tag-multiple-projects
[web] Handle adding/removing multiple projects from a tag at once GitOrigin-RevId: 7d052fa9930035286f8ce41433d6c3959817148a
This commit is contained in:
+208
-47
@@ -6,6 +6,7 @@ import ProjectListRoot from '../../../../../frontend/js/features/project-list/co
|
||||
import { renderWithProjectListContext } from '../helpers/render-with-context'
|
||||
import * as eventTracking from '../../../../../frontend/js/infrastructure/event-tracking'
|
||||
import {
|
||||
projectsData,
|
||||
owner,
|
||||
archivedProjects,
|
||||
makeLongProjectList,
|
||||
@@ -23,7 +24,15 @@ describe('<ProjectListRoot />', function () {
|
||||
global.localStorage.clear()
|
||||
sendSpy = sinon.spy(eventTracking, 'send')
|
||||
window.metaAttributesCache = new Map()
|
||||
window.metaAttributesCache.set('ol-tags', [])
|
||||
this.tagId = '999fff999fff'
|
||||
this.tagName = 'First tag name'
|
||||
window.metaAttributesCache.set('ol-tags', [
|
||||
{
|
||||
_id: this.tagId,
|
||||
name: this.tagName,
|
||||
project_ids: [projectsData[0].id, projectsData[1].id],
|
||||
},
|
||||
])
|
||||
window.metaAttributesCache.set('ol-ExposedSettings', {
|
||||
templateLinks: [],
|
||||
})
|
||||
@@ -138,16 +147,16 @@ describe('<ProjectListRoot />', function () {
|
||||
fireEvent.click(confirmBtn)
|
||||
expect(confirmBtn.disabled).to.be.true
|
||||
|
||||
await fetchMock.flush(true)
|
||||
expect(fetchMock.done()).to.be.true
|
||||
|
||||
const requests = fetchMock.calls()
|
||||
const [projectRequest1Url, projectRequest1Headers] = requests[2]
|
||||
expect(projectRequest1Url).to.equal(`/project/${project1Id}/archive`)
|
||||
expect(projectRequest1Headers?.method).to.equal('POST')
|
||||
const [projectRequest2Url, projectRequest2Headers] = requests[3]
|
||||
expect(projectRequest2Url).to.equal(`/project/${project2Id}/archive`)
|
||||
expect(projectRequest2Headers?.method).to.equal('POST')
|
||||
await waitFor(
|
||||
() =>
|
||||
expect(fetchMock.called(`/project/${project1Id}/archive`)).to.be
|
||||
.true
|
||||
)
|
||||
await waitFor(
|
||||
() =>
|
||||
expect(fetchMock.called(`/project/${project2Id}/archive`)).to.be
|
||||
.true
|
||||
)
|
||||
})
|
||||
|
||||
it('opens trash modal for all selected projects and trashes all', async function () {
|
||||
@@ -173,16 +182,16 @@ describe('<ProjectListRoot />', function () {
|
||||
fireEvent.click(confirmBtn)
|
||||
expect(confirmBtn.disabled).to.be.true
|
||||
|
||||
await fetchMock.flush(true)
|
||||
expect(fetchMock.done()).to.be.true
|
||||
|
||||
const requests = fetchMock.calls()
|
||||
const [projectRequest1Url, projectRequest1Headers] = requests[2]
|
||||
expect(projectRequest1Url).to.equal(`/project/${project1Id}/trash`)
|
||||
expect(projectRequest1Headers?.method).to.equal('POST')
|
||||
const [projectRequest2Url, projectRequest2Headers] = requests[3]
|
||||
expect(projectRequest2Url).to.equal(`/project/${project2Id}/trash`)
|
||||
expect(projectRequest2Headers?.method).to.equal('POST')
|
||||
await waitFor(
|
||||
() =>
|
||||
expect(fetchMock.called(`/project/${project1Id}/trash`)).to.be
|
||||
.true
|
||||
)
|
||||
await waitFor(
|
||||
() =>
|
||||
expect(fetchMock.called(`/project/${project2Id}/trash`)).to.be
|
||||
.true
|
||||
)
|
||||
})
|
||||
|
||||
it('only checks the projects that are viewable when there is a load more button', async function () {
|
||||
@@ -354,6 +363,141 @@ describe('<ProjectListRoot />', function () {
|
||||
})
|
||||
})
|
||||
|
||||
describe('tags dropdown', function () {
|
||||
beforeEach(async function () {
|
||||
allCheckboxes = screen.getAllByRole<HTMLInputElement>('checkbox')
|
||||
// first one is the select all checkbox
|
||||
fireEvent.click(allCheckboxes[1])
|
||||
fireEvent.click(allCheckboxes[2])
|
||||
actionsToolbar = screen.getAllByRole('toolbar')[0]
|
||||
|
||||
this.newTagName = 'Some tag name'
|
||||
this.newTagId = 'abc123def456'
|
||||
})
|
||||
|
||||
it('opens the tags dropdown and creates a new tag', async function () {
|
||||
fetchMock.post(`express:/tag`, {
|
||||
status: 200,
|
||||
body: {
|
||||
_id: this.newTagId,
|
||||
name: this.newTagName,
|
||||
project_ids: [],
|
||||
},
|
||||
})
|
||||
fetchMock.post(`express:/tag/:id/projects`, {
|
||||
status: 204,
|
||||
})
|
||||
|
||||
await waitFor(() => {
|
||||
const tagsDropdown = within(actionsToolbar).getByLabelText('Tags')
|
||||
fireEvent.click(tagsDropdown)
|
||||
})
|
||||
screen.getByText('Add to folder')
|
||||
|
||||
const newTagButton = screen.getByText('Create New Folder')
|
||||
fireEvent.click(newTagButton)
|
||||
|
||||
const modal = screen.getAllByRole('dialog')[0]
|
||||
const input = within(modal).getByRole<HTMLInputElement>('textbox')
|
||||
fireEvent.change(input, {
|
||||
target: { value: this.newTagName },
|
||||
})
|
||||
const createButton = within(modal).getByRole('button', {
|
||||
name: 'Create',
|
||||
})
|
||||
fireEvent.click(createButton)
|
||||
|
||||
await waitFor(
|
||||
() =>
|
||||
expect(fetchMock.called('/tag', { name: this.newTagName })).to.be
|
||||
.true
|
||||
)
|
||||
await waitFor(
|
||||
() =>
|
||||
expect(
|
||||
fetchMock.called(`/tag/${this.newTagId}/projects`, {
|
||||
body: {
|
||||
projectIds: [projectsData[0].id, projectsData[1].id],
|
||||
},
|
||||
})
|
||||
).to.be.true
|
||||
)
|
||||
|
||||
screen.getByRole('button', { name: `${this.newTagName} (2)` })
|
||||
})
|
||||
|
||||
it('opens the tags dropdown and remove a tag from selected projects', async function () {
|
||||
const deleteProjectsFromTagMock = fetchMock.delete(
|
||||
`express:/tag/:id/projects`,
|
||||
{
|
||||
status: 204,
|
||||
}
|
||||
)
|
||||
|
||||
screen.getByRole('button', { name: `${this.tagName} (2)` })
|
||||
|
||||
const tagsDropdown = within(actionsToolbar).getByLabelText('Tags')
|
||||
fireEvent.click(tagsDropdown)
|
||||
screen.getByText('Add to folder')
|
||||
|
||||
const tagButton = screen.getByLabelText(
|
||||
`Add or remove project from tag ${this.tagName}`
|
||||
)
|
||||
fireEvent.click(tagButton)
|
||||
|
||||
await waitFor(
|
||||
() =>
|
||||
expect(
|
||||
deleteProjectsFromTagMock.called(
|
||||
`/tag/${this.tagId}/projects`,
|
||||
{
|
||||
body: {
|
||||
projectIds: [projectsData[0].id, projectsData[1].id],
|
||||
},
|
||||
}
|
||||
)
|
||||
).to.be.true
|
||||
)
|
||||
|
||||
screen.getByRole('button', { name: `${this.tagName} (0)` })
|
||||
})
|
||||
|
||||
it('select another project, opens the tags dropdown and add a tag only to the untagged project', async function () {
|
||||
const addProjectsToTagMock = fetchMock.post(
|
||||
`express:/tag/:id/projects`,
|
||||
{
|
||||
status: 204,
|
||||
}
|
||||
)
|
||||
|
||||
fireEvent.click(allCheckboxes[3])
|
||||
|
||||
screen.getByRole('button', { name: `${this.tagName} (2)` })
|
||||
|
||||
const tagsDropdown = within(actionsToolbar).getByLabelText('Tags')
|
||||
fireEvent.click(tagsDropdown)
|
||||
screen.getByText('Add to folder')
|
||||
|
||||
const tagButton = screen.getByLabelText(
|
||||
`Add or remove project from tag ${this.tagName}`
|
||||
)
|
||||
fireEvent.click(tagButton)
|
||||
|
||||
await waitFor(
|
||||
() =>
|
||||
expect(
|
||||
addProjectsToTagMock.called(`/tag/${this.tagId}/projects`, {
|
||||
body: {
|
||||
projectIds: [projectsData[2].id],
|
||||
},
|
||||
})
|
||||
).to.be.true
|
||||
)
|
||||
|
||||
screen.getByRole('button', { name: `${this.tagName} (3)` })
|
||||
})
|
||||
})
|
||||
|
||||
describe('project tools "More" dropdown', function () {
|
||||
beforeEach(async function () {
|
||||
const filterButton = screen.getAllByText('All Projects')[0]
|
||||
@@ -374,9 +518,12 @@ describe('<ProjectListRoot />', function () {
|
||||
})
|
||||
|
||||
it('opens the rename modal, and can rename the project, and view updated', async function () {
|
||||
fetchMock.post(`express:/project/:id/rename`, {
|
||||
status: 200,
|
||||
})
|
||||
const renameProjectMock = fetchMock.post(
|
||||
`express:/project/:id/rename`,
|
||||
{
|
||||
status: 200,
|
||||
}
|
||||
)
|
||||
|
||||
await waitFor(() => {
|
||||
const moreDropdown =
|
||||
@@ -384,7 +531,8 @@ describe('<ProjectListRoot />', function () {
|
||||
fireEvent.click(moreDropdown)
|
||||
})
|
||||
|
||||
const renameButton = screen.getByText<HTMLInputElement>('Rename')
|
||||
const renameButton =
|
||||
screen.getAllByText<HTMLInputElement>('Rename')[1] // first one is for the tag in the sidebar
|
||||
fireEvent.click(renameButton)
|
||||
|
||||
const modal = screen.getAllByRole('dialog')[0]
|
||||
@@ -419,8 +567,14 @@ describe('<ProjectListRoot />', function () {
|
||||
expect(confirmButton.disabled).to.be.false
|
||||
fireEvent.click(confirmButton)
|
||||
|
||||
await fetchMock.flush(true)
|
||||
expect(fetchMock.done()).to.be.true
|
||||
await waitFor(
|
||||
() =>
|
||||
expect(
|
||||
renameProjectMock.called(
|
||||
`/project/${projectsData[1].id}/rename`
|
||||
)
|
||||
).to.be.true
|
||||
)
|
||||
|
||||
screen.findByText(newProjectName)
|
||||
expect(screen.queryByText(oldName)).to.be.null
|
||||
@@ -435,24 +589,27 @@ describe('<ProjectListRoot />', function () {
|
||||
const tableRows = screen.getAllByRole('row')
|
||||
const linkForProjectToCopy = within(tableRows[1]).getByRole('link')
|
||||
const projectNameToCopy = linkForProjectToCopy.textContent || '' // needed for type checking
|
||||
screen.findByText(projectNameToCopy) // make sure not just empty string
|
||||
screen.getByText(projectNameToCopy) // make sure not just empty string
|
||||
const copiedProjectName = `${projectNameToCopy} (Copy)`
|
||||
fetchMock.post(`express:/project/:id/clone`, {
|
||||
status: 200,
|
||||
body: {
|
||||
name: copiedProjectName,
|
||||
lastUpdated: new Date(),
|
||||
project_id: userId,
|
||||
owner_ref: userId,
|
||||
owner,
|
||||
id: '6328e14abec0df019fce0be5',
|
||||
lastUpdatedBy: owner,
|
||||
accessLevel: 'owner',
|
||||
source: 'owner',
|
||||
trashed: false,
|
||||
archived: false,
|
||||
},
|
||||
})
|
||||
const cloneProjectMock = fetchMock.post(
|
||||
`express:/project/:id/clone`,
|
||||
{
|
||||
status: 200,
|
||||
body: {
|
||||
name: copiedProjectName,
|
||||
lastUpdated: new Date(),
|
||||
project_id: userId,
|
||||
owner_ref: userId,
|
||||
owner,
|
||||
id: '6328e14abec0df019fce0be5',
|
||||
lastUpdatedBy: owner,
|
||||
accessLevel: 'owner',
|
||||
source: 'owner',
|
||||
trashed: false,
|
||||
archived: false,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
await waitFor(() => {
|
||||
const moreDropdown =
|
||||
@@ -470,13 +627,17 @@ describe('<ProjectListRoot />', function () {
|
||||
) as HTMLElement
|
||||
fireEvent.click(copyConfirmButton)
|
||||
|
||||
await fetchMock.flush(true)
|
||||
expect(fetchMock.done()).to.be.true
|
||||
await waitFor(
|
||||
() =>
|
||||
expect(
|
||||
cloneProjectMock.called(`/project/${projectsData[1].id}/clone`)
|
||||
).to.be.true
|
||||
)
|
||||
|
||||
expect(sendSpy).to.be.calledOnce
|
||||
expect(sendSpy).calledWith('project-list-page-interaction')
|
||||
|
||||
screen.findByText(copiedProjectName)
|
||||
screen.getByText(copiedProjectName)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
+2
-1
@@ -27,6 +27,7 @@ describe('<TagsList />', function () {
|
||||
name: 'New Tag',
|
||||
project_ids: [],
|
||||
})
|
||||
fetchMock.post('express:/tag/:tagId/projects', 200)
|
||||
fetchMock.post('express:/tag/:tagId/rename', 200)
|
||||
fetchMock.delete('express:/tag/:tagId', 200)
|
||||
|
||||
@@ -145,7 +146,7 @@ describe('<TagsList />', function () {
|
||||
|
||||
await fireEvent.click(createButton)
|
||||
|
||||
await waitFor(() => expect(fetchMock.called(`/tag`)))
|
||||
await waitFor(() => expect(fetchMock.called(`/tag`)).to.be.true)
|
||||
|
||||
expect(screen.queryByRole('dialog', { hidden: false })).to.be.null
|
||||
|
||||
|
||||
+9
-12
@@ -1,5 +1,5 @@
|
||||
import { expect } from 'chai'
|
||||
import { fireEvent, screen } from '@testing-library/react'
|
||||
import { fireEvent, screen, waitFor } from '@testing-library/react'
|
||||
import { ArchiveProjectButtonTooltip } from '../../../../../../../../frontend/js/features/project-list/components/table/cells/action-buttons/archive-project-button'
|
||||
import {
|
||||
archiveableProject,
|
||||
@@ -44,8 +44,8 @@ describe('<ArchiveProjectButton />', function () {
|
||||
|
||||
it('should archive the projects', async function () {
|
||||
const project = Object.assign({}, archiveableProject)
|
||||
fetchMock.post(
|
||||
`express:/project/${project.id}/archive`,
|
||||
const archiveProjectMock = fetchMock.post(
|
||||
`express:/project/:projectId/archive`,
|
||||
{
|
||||
status: 200,
|
||||
},
|
||||
@@ -62,14 +62,11 @@ describe('<ArchiveProjectButton />', function () {
|
||||
const confirmBtn = screen.getByText('Confirm') as HTMLButtonElement
|
||||
fireEvent.click(confirmBtn)
|
||||
expect(confirmBtn.disabled).to.be.true
|
||||
// verify archived
|
||||
await fetchMock.flush(true)
|
||||
expect(fetchMock.done()).to.be.true
|
||||
const requests = fetchMock.calls()
|
||||
// first mock call is to get list of projects in projectlistcontext
|
||||
const [requestUrl, requestHeaders] = requests[1]
|
||||
expect(requestUrl).to.equal(`/project/${project.id}/archive`)
|
||||
expect(requestHeaders?.method).to.equal('POST')
|
||||
fetchMock.reset()
|
||||
|
||||
await waitFor(
|
||||
() =>
|
||||
expect(archiveProjectMock.called(`/project/${project.id}/archive`)).to
|
||||
.be.true
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
+10
-12
@@ -1,5 +1,5 @@
|
||||
import { expect } from 'chai'
|
||||
import { fireEvent, screen } from '@testing-library/react'
|
||||
import { fireEvent, screen, waitFor } from '@testing-library/react'
|
||||
import { CopyProjectButtonTooltip } from '../../../../../../../../frontend/js/features/project-list/components/table/cells/action-buttons/copy-project-button'
|
||||
import {
|
||||
archivedProject,
|
||||
@@ -16,6 +16,7 @@ describe('<CopyProjectButton />', function () {
|
||||
afterEach(function () {
|
||||
resetProjectListContextFetch()
|
||||
})
|
||||
|
||||
it('renders tooltip for button', function () {
|
||||
renderWithProjectListContext(
|
||||
<CopyProjectButtonTooltip project={copyableProject} />
|
||||
@@ -40,8 +41,8 @@ describe('<CopyProjectButton />', function () {
|
||||
})
|
||||
|
||||
it('opens the modal and copies the project ', async function () {
|
||||
fetchMock.post(
|
||||
`express:/project/${copyableProject.id}/clone`,
|
||||
const copyProjectMock = fetchMock.post(
|
||||
`express:/project/:projectId/clone`,
|
||||
{
|
||||
status: 200,
|
||||
},
|
||||
@@ -58,14 +59,11 @@ describe('<CopyProjectButton />', function () {
|
||||
const copyBtn = screen.getByText('Copy') as HTMLButtonElement
|
||||
fireEvent.click(copyBtn)
|
||||
expect(copyBtn.disabled).to.be.true
|
||||
// verify cloned
|
||||
await fetchMock.flush(true)
|
||||
expect(fetchMock.done()).to.be.true
|
||||
const requests = fetchMock.calls()
|
||||
// first mock call is to get list of projects in projectlistcontext
|
||||
const [requestUrl, requestHeaders] = requests[1]
|
||||
expect(requestUrl).to.equal(`/project/${copyableProject.id}/clone`)
|
||||
expect(requestHeaders?.method).to.equal('POST')
|
||||
fetchMock.reset()
|
||||
|
||||
await waitFor(
|
||||
() =>
|
||||
expect(copyProjectMock.called(`/project/${copyableProject.id}/clone`))
|
||||
.to.be.true
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
+8
-12
@@ -1,5 +1,5 @@
|
||||
import { expect } from 'chai'
|
||||
import { fireEvent, screen } from '@testing-library/react'
|
||||
import { fireEvent, screen, waitFor } from '@testing-library/react'
|
||||
import { DeleteProjectButtonTooltip } from '../../../../../../../../frontend/js/features/project-list/components/table/cells/action-buttons/delete-project-button'
|
||||
import {
|
||||
archiveableProject,
|
||||
@@ -46,8 +46,8 @@ describe('<DeleteProjectButton />', function () {
|
||||
it('opens the modal and deletes the project', async function () {
|
||||
window.user_id = trashedProject?.owner?.id
|
||||
const project = Object.assign({}, trashedProject)
|
||||
fetchMock.delete(
|
||||
`express:/project/${project.id}`,
|
||||
const deleteProjectMock = fetchMock.delete(
|
||||
`express:/project/:projectId`,
|
||||
{
|
||||
status: 200,
|
||||
},
|
||||
@@ -64,14 +64,10 @@ describe('<DeleteProjectButton />', function () {
|
||||
const confirmBtn = screen.getByText('Confirm') as HTMLButtonElement
|
||||
fireEvent.click(confirmBtn)
|
||||
expect(confirmBtn.disabled).to.be.true
|
||||
// verify trashed
|
||||
await fetchMock.flush(true)
|
||||
expect(fetchMock.done()).to.be.true
|
||||
const requests = fetchMock.calls()
|
||||
// first request is project list api in projectlistcontext
|
||||
const [requestUrl, requestHeaders] = requests[1]
|
||||
expect(requestUrl).to.equal(`/project/${project.id}`)
|
||||
expect(requestHeaders?.method).to.equal('DELETE')
|
||||
fetchMock.reset()
|
||||
|
||||
await waitFor(
|
||||
() =>
|
||||
expect(deleteProjectMock.called(`/project/${project.id}`)).to.be.true
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
+9
-11
@@ -1,5 +1,5 @@
|
||||
import { expect } from 'chai'
|
||||
import { fireEvent, screen } from '@testing-library/react'
|
||||
import { fireEvent, screen, waitFor } from '@testing-library/react'
|
||||
import { LeaveProjectButtonTooltip } from '../../../../../../../../frontend/js/features/project-list/components/table/cells/action-buttons/leave-project-button'
|
||||
import {
|
||||
trashedProject,
|
||||
@@ -17,6 +17,7 @@ describe('<LeaveProjectButtton />', function () {
|
||||
afterEach(function () {
|
||||
resetProjectListContextFetch()
|
||||
})
|
||||
|
||||
it('renders tooltip for button', function () {
|
||||
renderWithProjectListContext(
|
||||
<LeaveProjectButtonTooltip project={trashedAndNotOwnedProject} />
|
||||
@@ -51,7 +52,7 @@ describe('<LeaveProjectButtton />', function () {
|
||||
|
||||
it('opens the modal and leaves the project', async function () {
|
||||
const project = Object.assign({}, trashedAndNotOwnedProject)
|
||||
fetchMock.post(
|
||||
const leaveProjectMock = fetchMock.post(
|
||||
`express:/project/${project.id}/leave`,
|
||||
{
|
||||
status: 200,
|
||||
@@ -69,14 +70,11 @@ describe('<LeaveProjectButtton />', function () {
|
||||
const confirmBtn = screen.getByText('Confirm') as HTMLButtonElement
|
||||
fireEvent.click(confirmBtn)
|
||||
expect(confirmBtn.disabled).to.be.true
|
||||
// verify trashed
|
||||
await fetchMock.flush(true)
|
||||
expect(fetchMock.done()).to.be.true
|
||||
const requests = fetchMock.calls()
|
||||
// first request is project list api in projectlistcontext
|
||||
const [requestUrl, requestHeaders] = requests[1]
|
||||
expect(requestUrl).to.equal(`/project/${project.id}/leave`)
|
||||
expect(requestHeaders?.method).to.equal('POST')
|
||||
fetchMock.reset()
|
||||
|
||||
await waitFor(
|
||||
() =>
|
||||
expect(leaveProjectMock.called(`/project/${project.id}/leave`)).to.be
|
||||
.true
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
+9
-12
@@ -1,5 +1,5 @@
|
||||
import { expect } from 'chai'
|
||||
import { fireEvent, screen } from '@testing-library/react'
|
||||
import { fireEvent, screen, waitFor } from '@testing-library/react'
|
||||
import { TrashProjectButtonTooltip } from '../../../../../../../../frontend/js/features/project-list/components/table/cells/action-buttons/trash-project-button'
|
||||
import {
|
||||
archivedProject,
|
||||
@@ -34,8 +34,8 @@ describe('<TrashProjectButton />', function () {
|
||||
|
||||
it('opens the modal and trashes the project', async function () {
|
||||
const project = Object.assign({}, archivedProject)
|
||||
fetchMock.post(
|
||||
`express:/project/${project.id}/trash`,
|
||||
const trashProjectMock = fetchMock.post(
|
||||
`express:/project/:projectId/trash`,
|
||||
{
|
||||
status: 200,
|
||||
},
|
||||
@@ -52,14 +52,11 @@ describe('<TrashProjectButton />', function () {
|
||||
const confirmBtn = screen.getByText('Confirm') as HTMLButtonElement
|
||||
fireEvent.click(confirmBtn)
|
||||
expect(confirmBtn.disabled).to.be.true
|
||||
// verify trashed
|
||||
await fetchMock.flush(true)
|
||||
expect(fetchMock.done()).to.be.true
|
||||
const requests = fetchMock.calls()
|
||||
// first request is to get list of projects in projectlistcontext
|
||||
const [requestUrl, requestHeaders] = requests[1]
|
||||
expect(requestUrl).to.equal(`/project/${project.id}/trash`)
|
||||
expect(requestHeaders?.method).to.equal('POST')
|
||||
fetchMock.reset()
|
||||
|
||||
await waitFor(
|
||||
() =>
|
||||
expect(trashProjectMock.called(`/project/${project.id}/trash`)).to.be
|
||||
.true
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
+8
-5
@@ -1,5 +1,5 @@
|
||||
import { expect } from 'chai'
|
||||
import { fireEvent, screen } from '@testing-library/react'
|
||||
import { fireEvent, screen, waitFor } from '@testing-library/react'
|
||||
import { UnarchiveProjectButtonTooltip } from '../../../../../../../../frontend/js/features/project-list/components/table/cells/action-buttons/unarchive-project-button'
|
||||
import {
|
||||
archiveableProject,
|
||||
@@ -42,8 +42,8 @@ describe('<UnarchiveProjectButton />', function () {
|
||||
|
||||
it('unarchive the project and updates the view data', async function () {
|
||||
const project = Object.assign({}, archivedProject)
|
||||
fetchMock.delete(
|
||||
`express:/project/${project.id}/archive`,
|
||||
const unarchiveProjectMock = fetchMock.delete(
|
||||
`express:/project/:projectId/archive`,
|
||||
{
|
||||
status: 200,
|
||||
},
|
||||
@@ -55,7 +55,10 @@ describe('<UnarchiveProjectButton />', function () {
|
||||
const btn = screen.getByLabelText('Restore')
|
||||
fireEvent.click(btn)
|
||||
|
||||
await fetchMock.flush(true)
|
||||
expect(fetchMock.done()).to.be.true
|
||||
await waitFor(
|
||||
() =>
|
||||
expect(unarchiveProjectMock.called(`/project/${project.id}/archive`)).to
|
||||
.be.true
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
+8
-9
@@ -1,5 +1,5 @@
|
||||
import { expect } from 'chai'
|
||||
import { fireEvent, screen } from '@testing-library/react'
|
||||
import { fireEvent, screen, waitFor } from '@testing-library/react'
|
||||
import fetchMock from 'fetch-mock'
|
||||
import { UntrashProjectButtonTooltip } from '../../../../../../../../frontend/js/features/project-list/components/table/cells/action-buttons/untrash-project-button'
|
||||
import {
|
||||
@@ -12,10 +12,6 @@ import {
|
||||
} from '../../../../helpers/render-with-context'
|
||||
|
||||
describe('<UntrashProjectButton />', function () {
|
||||
beforeEach(function () {
|
||||
fetchMock.reset()
|
||||
})
|
||||
|
||||
afterEach(function () {
|
||||
resetProjectListContextFetch()
|
||||
})
|
||||
@@ -38,8 +34,8 @@ describe('<UntrashProjectButton />', function () {
|
||||
|
||||
it('untrashes the project and updates the view data', async function () {
|
||||
const project = Object.assign({}, trashedProject)
|
||||
fetchMock.delete(
|
||||
`express:/project/${project.id}/trash`,
|
||||
const untrashProjectMock = fetchMock.delete(
|
||||
`express:/project/:projectId/trash`,
|
||||
{
|
||||
status: 200,
|
||||
},
|
||||
@@ -51,7 +47,10 @@ describe('<UntrashProjectButton />', function () {
|
||||
const btn = screen.getByLabelText('Restore')
|
||||
fireEvent.click(btn)
|
||||
|
||||
await fetchMock.flush(true)
|
||||
expect(fetchMock.done()).to.be.true
|
||||
await waitFor(
|
||||
() =>
|
||||
expect(untrashProjectMock.called(`/project/${project.id}/trash`)).to.be
|
||||
.true
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
+24
-11
@@ -1,4 +1,4 @@
|
||||
import { fireEvent, screen, within } from '@testing-library/react'
|
||||
import { fireEvent, screen, waitFor, within } from '@testing-library/react'
|
||||
import { expect } from 'chai'
|
||||
import RenameProjectModal from '../../../../../../../frontend/js/features/project-list/components/modals/rename-project-modal'
|
||||
import {
|
||||
@@ -9,14 +9,21 @@ import { currentProjects } from '../../../fixtures/projects-data'
|
||||
import fetchMock from 'fetch-mock'
|
||||
|
||||
describe('<RenameProjectModal />', function () {
|
||||
beforeEach(function () {
|
||||
resetProjectListContextFetch()
|
||||
})
|
||||
|
||||
afterEach(function () {
|
||||
resetProjectListContextFetch()
|
||||
})
|
||||
|
||||
it('renders the modal and validates new name', async function () {
|
||||
fetchMock.post('express:/project/:projectId/rename', {
|
||||
status: 200,
|
||||
})
|
||||
const renameProjectMock = fetchMock.post(
|
||||
'express:/project/:projectId/rename',
|
||||
{
|
||||
status: 200,
|
||||
}
|
||||
)
|
||||
renderWithProjectListContext(
|
||||
<RenameProjectModal
|
||||
handleCloseModal={() => {}}
|
||||
@@ -44,14 +51,21 @@ describe('<RenameProjectModal />', function () {
|
||||
fireEvent.click(submitButton)
|
||||
expect(submitButton.disabled).to.be.true
|
||||
|
||||
await fetchMock.flush(true)
|
||||
expect(fetchMock.done()).to.be.true
|
||||
await waitFor(
|
||||
() =>
|
||||
expect(
|
||||
renameProjectMock.called(`/project/${currentProjects[0].id}/rename`)
|
||||
).to.be.true
|
||||
)
|
||||
})
|
||||
|
||||
it('shows error message from API', async function () {
|
||||
fetchMock.post('express:/project/:projectId/rename', {
|
||||
status: 500,
|
||||
})
|
||||
const postRenameMock = fetchMock.post(
|
||||
'express:/project/:projectId/rename',
|
||||
{
|
||||
status: 500,
|
||||
}
|
||||
)
|
||||
renderWithProjectListContext(
|
||||
<RenameProjectModal
|
||||
handleCloseModal={() => {}}
|
||||
@@ -70,8 +84,7 @@ describe('<RenameProjectModal />', function () {
|
||||
const submitButton = within(modal).getByText('Rename') as HTMLButtonElement
|
||||
fireEvent.click(submitButton)
|
||||
|
||||
await fetchMock.flush(true)
|
||||
expect(fetchMock.done()).to.be.true
|
||||
await waitFor(() => expect(postRenameMock.called()).to.be.true)
|
||||
|
||||
screen.getByText('Something went wrong. Please try again.')
|
||||
})
|
||||
|
||||
@@ -24,6 +24,11 @@ export function renderWithProjectListContext(
|
||||
body: { projects, totalSize: projects.length },
|
||||
})
|
||||
|
||||
fetchMock.get('express:/system/messages', {
|
||||
status: 200,
|
||||
body: [],
|
||||
})
|
||||
|
||||
const ProjectListProviderWrapper = ({
|
||||
children,
|
||||
}: {
|
||||
|
||||
@@ -14,7 +14,9 @@ describe('TagsController', function () {
|
||||
this.TagsHandler = {
|
||||
promises: {
|
||||
addProjectToTag: sinon.stub().resolves(),
|
||||
addProjectsToTag: sinon.stub().resolves(),
|
||||
removeProjectFromTag: sinon.stub().resolves(),
|
||||
removeProjectsFromTag: sinon.stub().resolves(),
|
||||
deleteTag: sinon.stub().resolves(),
|
||||
renameTag: sinon.stub().resolves(),
|
||||
createTag: sinon.stub().resolves(),
|
||||
@@ -40,6 +42,7 @@ describe('TagsController', function () {
|
||||
_id: userId,
|
||||
},
|
||||
},
|
||||
body: {},
|
||||
}
|
||||
|
||||
this.res = {}
|
||||
@@ -163,6 +166,30 @@ describe('TagsController', function () {
|
||||
})
|
||||
})
|
||||
|
||||
it('add projects to a tag', function (done) {
|
||||
this.req.params.tagId = this.tagId = 'tag-id-123'
|
||||
this.req.body.projectIds = this.projectIds = [
|
||||
'project-id-123',
|
||||
'project-id-234',
|
||||
]
|
||||
this.req.session.user._id = this.userId = 'user-id-123'
|
||||
this.TagsController.addProjectsToTag(this.req, {
|
||||
status: code => {
|
||||
assert.equal(code, 204)
|
||||
sinon.assert.calledWith(
|
||||
this.TagsHandler.promises.addProjectsToTag,
|
||||
this.userId,
|
||||
this.tagId,
|
||||
this.projectIds
|
||||
)
|
||||
done()
|
||||
return {
|
||||
end: () => {},
|
||||
}
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it('remove a project from a tag', function (done) {
|
||||
this.req.params.tagId = this.tagId = 'tag-id-123'
|
||||
this.req.params.projectId = this.projectId = 'project-id-123'
|
||||
@@ -183,4 +210,28 @@ describe('TagsController', function () {
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it('remove projects from a tag', function (done) {
|
||||
this.req.params.tagId = this.tagId = 'tag-id-123'
|
||||
this.req.body.projectIds = this.projectIds = [
|
||||
'project-id-123',
|
||||
'project-id-234',
|
||||
]
|
||||
this.req.session.user._id = this.userId = 'user-id-123'
|
||||
this.TagsController.removeProjectsFromTag(this.req, {
|
||||
status: code => {
|
||||
assert.equal(code, 204)
|
||||
sinon.assert.calledWith(
|
||||
this.TagsHandler.promises.removeProjectsFromTag,
|
||||
this.userId,
|
||||
this.tagId,
|
||||
this.projectIds
|
||||
)
|
||||
done()
|
||||
return {
|
||||
end: () => {},
|
||||
}
|
||||
},
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user