mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-29 11:53:40 -05:00
Merge pull request #9865 from overleaf/ab-display-notifications-welcome-page
[web] Display notifications on the react dashboard welcome page GitOrigin-RevId: 29fb08bbac195c2766dd0e94dbe9e9a0c7065e76
This commit is contained in:
parent
dce00bbefe
commit
debe76baa6
4 changed files with 438 additions and 389 deletions
|
@ -5,7 +5,6 @@ import Notification from '../notification'
|
||||||
import Icon from '../../../../../shared/components/icon'
|
import Icon from '../../../../../shared/components/icon'
|
||||||
import getMeta from '../../../../../utils/meta'
|
import getMeta from '../../../../../utils/meta'
|
||||||
import useAsyncDismiss from '../hooks/useAsyncDismiss'
|
import useAsyncDismiss from '../hooks/useAsyncDismiss'
|
||||||
import { useProjectListContext } from '../../../context/project-list-context'
|
|
||||||
import useAsync from '../../../../../shared/hooks/use-async'
|
import useAsync from '../../../../../shared/hooks/use-async'
|
||||||
import { FetchError, postJSON } from '../../../../../infrastructure/fetch-json'
|
import { FetchError, postJSON } from '../../../../../infrastructure/fetch-json'
|
||||||
import { ExposedSettings } from '../../../../../../../types/exposed-settings'
|
import { ExposedSettings } from '../../../../../../../types/exposed-settings'
|
||||||
|
@ -14,7 +13,6 @@ import { User } from '../../../../../../../types/user'
|
||||||
|
|
||||||
function Common() {
|
function Common() {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { totalProjectsCount } = useProjectListContext()
|
|
||||||
const { samlInitPath } = getMeta('ol-ExposedSettings') as ExposedSettings
|
const { samlInitPath } = getMeta('ol-ExposedSettings') as ExposedSettings
|
||||||
const notifications = getMeta('ol-notifications', []) as NotificationType[]
|
const notifications = getMeta('ol-notifications', []) as NotificationType[]
|
||||||
const user = getMeta('ol-user', []) as Pick<User, 'features'>
|
const user = getMeta('ol-user', []) as Pick<User, 'features'>
|
||||||
|
@ -33,7 +31,7 @@ function Common() {
|
||||||
).catch(console.error)
|
).catch(console.error)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!totalProjectsCount || !notifications.length) {
|
if (!notifications.length) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -135,6 +135,11 @@ function ProjectListPageContent() {
|
||||||
mdOffset={2}
|
mdOffset={2}
|
||||||
className="project-list-empty-col"
|
className="project-list-empty-col"
|
||||||
>
|
>
|
||||||
|
<Row>
|
||||||
|
<Col xs={12}>
|
||||||
|
<UserNotifications />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
<WelcomeMessage />
|
<WelcomeMessage />
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
|
@ -24,20 +24,17 @@ describe('<ProjectListRoot />', function () {
|
||||||
sendSpy = sinon.spy(eventTracking, 'send')
|
sendSpy = sinon.spy(eventTracking, 'send')
|
||||||
window.metaAttributesCache = new Map()
|
window.metaAttributesCache = new Map()
|
||||||
window.metaAttributesCache.set('ol-tags', [])
|
window.metaAttributesCache.set('ol-tags', [])
|
||||||
window.metaAttributesCache.set('ol-ExposedSettings', { templateLinks: [] })
|
window.metaAttributesCache.set('ol-ExposedSettings', {
|
||||||
|
templateLinks: [],
|
||||||
|
})
|
||||||
|
window.metaAttributesCache.set('ol-userEmails', [
|
||||||
|
{ email: 'test@overleaf.com', default: true },
|
||||||
|
])
|
||||||
window.user_id = userId
|
window.user_id = userId
|
||||||
|
|
||||||
Object.defineProperty(window, 'location', {
|
Object.defineProperty(window, 'location', {
|
||||||
value: { assign: locationStub },
|
value: { assign: locationStub },
|
||||||
})
|
})
|
||||||
|
|
||||||
renderWithProjectListContext(<ProjectListRoot />, {
|
|
||||||
projects: fullList,
|
|
||||||
})
|
|
||||||
await fetchMock.flush(true)
|
|
||||||
await waitFor(() => {
|
|
||||||
screen.findByRole('table')
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
afterEach(function () {
|
afterEach(function () {
|
||||||
|
@ -49,360 +46,465 @@ describe('<ProjectListRoot />', function () {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('checkboxes', function () {
|
describe('welcome page', function () {
|
||||||
let allCheckboxes: Array<HTMLInputElement> = []
|
beforeEach(async function () {
|
||||||
let actionsToolbar: HTMLElement
|
renderWithProjectListContext(<ProjectListRoot />, {
|
||||||
let project1Id: string | null, project2Id: string | null
|
projects: [],
|
||||||
|
|
||||||
describe('all projects', function () {
|
|
||||||
beforeEach(function () {
|
|
||||||
allCheckboxes = screen.getAllByRole<HTMLInputElement>('checkbox')
|
|
||||||
// first one is the select all checkbox
|
|
||||||
fireEvent.click(allCheckboxes[1])
|
|
||||||
fireEvent.click(allCheckboxes[2])
|
|
||||||
|
|
||||||
project1Id = allCheckboxes[1].getAttribute('data-project-id')
|
|
||||||
project2Id = allCheckboxes[2].getAttribute('data-project-id')
|
|
||||||
actionsToolbar = screen.getAllByRole('toolbar')[0]
|
|
||||||
})
|
})
|
||||||
|
await fetchMock.flush(true)
|
||||||
|
})
|
||||||
|
|
||||||
it('downloads all selected projects and then unselects them', async function () {
|
it('the welcome page is displayed', async function () {
|
||||||
const downloadButton = within(actionsToolbar).getByLabelText('Download')
|
screen.getByRole('heading', { name: 'Welcome to Overleaf!' })
|
||||||
fireEvent.click(downloadButton)
|
})
|
||||||
|
|
||||||
await waitFor(() => {
|
it('the email confirmation alert is not displayed', async function () {
|
||||||
expect(locationStub).to.have.been.called
|
expect(
|
||||||
})
|
screen.queryByText(
|
||||||
|
'Please confirm your email test@overleaf.com by clicking on the link in the confirmation email'
|
||||||
sinon.assert.calledWithMatch(
|
|
||||||
locationStub,
|
|
||||||
`/project/download/zip?project_ids=${project1Id},${project2Id}`
|
|
||||||
)
|
)
|
||||||
|
).to.be.null
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
const allCheckboxes = screen.getAllByRole<HTMLInputElement>('checkbox')
|
describe('project table', function () {
|
||||||
const allCheckboxesChecked = allCheckboxes.filter(c => c.checked)
|
beforeEach(async function () {
|
||||||
expect(allCheckboxesChecked.length).to.equal(0)
|
renderWithProjectListContext(<ProjectListRoot />, {
|
||||||
|
projects: fullList,
|
||||||
})
|
})
|
||||||
|
await fetchMock.flush(true)
|
||||||
it('opens archive modal for all selected projects and archives all', async function () {
|
await waitFor(() => {
|
||||||
fetchMock.post(
|
screen.findByRole('table')
|
||||||
`express:/project/${project1Id}/archive`,
|
|
||||||
{
|
|
||||||
status: 200,
|
|
||||||
},
|
|
||||||
{ delay: 0 }
|
|
||||||
)
|
|
||||||
fetchMock.post(
|
|
||||||
`express:/project/${project2Id}/archive`,
|
|
||||||
{
|
|
||||||
status: 200,
|
|
||||||
},
|
|
||||||
{ delay: 0 }
|
|
||||||
)
|
|
||||||
|
|
||||||
const archiveButton = within(actionsToolbar).getByLabelText('Archive')
|
|
||||||
fireEvent.click(archiveButton)
|
|
||||||
|
|
||||||
const confirmBtn = screen.getByText('Confirm') as HTMLButtonElement
|
|
||||||
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')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('opens trash modal for all selected projects and trashes all', async function () {
|
|
||||||
fetchMock.post(
|
|
||||||
`express:/project/${project1Id}/trash`,
|
|
||||||
{
|
|
||||||
status: 200,
|
|
||||||
},
|
|
||||||
{ delay: 0 }
|
|
||||||
)
|
|
||||||
fetchMock.post(
|
|
||||||
`express:/project/${project2Id}/trash`,
|
|
||||||
{
|
|
||||||
status: 200,
|
|
||||||
},
|
|
||||||
{ delay: 0 }
|
|
||||||
)
|
|
||||||
|
|
||||||
const archiveButton = within(actionsToolbar).getByLabelText('Trash')
|
|
||||||
fireEvent.click(archiveButton)
|
|
||||||
|
|
||||||
const confirmBtn = screen.getByText('Confirm') as HTMLButtonElement
|
|
||||||
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')
|
|
||||||
})
|
|
||||||
|
|
||||||
it('only checks the projects that are viewable when there is a load more button', async function () {
|
|
||||||
// first one is the select all checkbox
|
|
||||||
fireEvent.click(allCheckboxes[0])
|
|
||||||
|
|
||||||
allCheckboxes = screen.getAllByRole<HTMLInputElement>('checkbox')
|
|
||||||
let checked = allCheckboxes.filter(c => c.checked)
|
|
||||||
expect(checked.length).to.equal(21) // max projects viewable by default is 20, and plus one for check all
|
|
||||||
|
|
||||||
const loadMoreButton = screen.getByLabelText('Show 17 more projects')
|
|
||||||
fireEvent.click(loadMoreButton)
|
|
||||||
|
|
||||||
allCheckboxes = screen.getAllByRole<HTMLInputElement>('checkbox')
|
|
||||||
expect(allCheckboxes.length).to.equal(currentList.length + 1)
|
|
||||||
checked = allCheckboxes.filter(c => c.checked)
|
|
||||||
expect(checked.length).to.equal(20) // remains same even after showing more
|
|
||||||
})
|
|
||||||
|
|
||||||
it('maintains viewable and selected projects after loading more and then selecting all', async function () {
|
|
||||||
const loadMoreButton = screen.getByLabelText('Show 17 more projects')
|
|
||||||
fireEvent.click(loadMoreButton)
|
|
||||||
// verify button gone
|
|
||||||
screen.getByText(
|
|
||||||
`Showing ${currentList.length} out of ${currentList.length} projects.`
|
|
||||||
)
|
|
||||||
// first one is the select all checkbox
|
|
||||||
fireEvent.click(allCheckboxes[0])
|
|
||||||
// verify button still gone
|
|
||||||
screen.getByText(
|
|
||||||
`Showing ${currentList.length} out of ${currentList.length} projects.`
|
|
||||||
)
|
|
||||||
|
|
||||||
allCheckboxes = screen.getAllByRole<HTMLInputElement>('checkbox')
|
|
||||||
expect(allCheckboxes.length).to.equal(currentList.length + 1)
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('archived projects', function () {
|
describe('checkboxes', function () {
|
||||||
beforeEach(function () {
|
let allCheckboxes: Array<HTMLInputElement> = []
|
||||||
const filterButton = screen.getAllByText('Archived Projects')[0]
|
let actionsToolbar: HTMLElement
|
||||||
fireEvent.click(filterButton)
|
let project1Id: string | null, project2Id: string | null
|
||||||
|
|
||||||
allCheckboxes = screen.getAllByRole<HTMLInputElement>('checkbox')
|
describe('all projects', function () {
|
||||||
expect(allCheckboxes.length === 2).to.be.true
|
beforeEach(function () {
|
||||||
// first one is the select all checkbox
|
allCheckboxes = screen.getAllByRole<HTMLInputElement>('checkbox')
|
||||||
fireEvent.click(allCheckboxes[1])
|
// first one is the select all checkbox
|
||||||
project1Id = allCheckboxes[1].getAttribute('data-project-id')
|
fireEvent.click(allCheckboxes[1])
|
||||||
|
fireEvent.click(allCheckboxes[2])
|
||||||
|
|
||||||
actionsToolbar = screen.getAllByRole('toolbar')[0]
|
project1Id = allCheckboxes[1].getAttribute('data-project-id')
|
||||||
})
|
project2Id = allCheckboxes[2].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 =
|
it('downloads all selected projects and then unselects them', async function () {
|
||||||
within(actionsToolbar).getByText<HTMLInputElement>('Restore')
|
const downloadButton =
|
||||||
fireEvent.click(unarchiveButton)
|
within(actionsToolbar).getByLabelText('Download')
|
||||||
|
fireEvent.click(downloadButton)
|
||||||
|
|
||||||
await fetchMock.flush(true)
|
await waitFor(() => {
|
||||||
expect(fetchMock.done()).to.be.true
|
expect(locationStub).to.have.been.called
|
||||||
|
})
|
||||||
|
|
||||||
screen.getByText('No projects')
|
sinon.assert.calledWithMatch(
|
||||||
})
|
locationStub,
|
||||||
|
`/project/download/zip?project_ids=${project1Id},${project2Id}`
|
||||||
|
)
|
||||||
|
|
||||||
it('only unarchive the selected projects', async function () {
|
const allCheckboxes =
|
||||||
// beforeEach selected all, so uncheck the 1st project
|
screen.getAllByRole<HTMLInputElement>('checkbox')
|
||||||
fireEvent.click(allCheckboxes[1])
|
const allCheckboxesChecked = allCheckboxes.filter(c => c.checked)
|
||||||
|
expect(allCheckboxesChecked.length).to.equal(0)
|
||||||
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 () {
|
|
||||||
beforeEach(function () {
|
|
||||||
const filterButton = screen.getAllByText('Trashed Projects')[0]
|
|
||||||
fireEvent.click(filterButton)
|
|
||||||
|
|
||||||
allCheckboxes = screen.getAllByRole<HTMLInputElement>('checkbox')
|
|
||||||
// + 1 because of select all
|
|
||||||
expect(allCheckboxes.length).to.equal(trashedList.length + 1)
|
|
||||||
|
|
||||||
// first one is the select all checkbox
|
|
||||||
fireEvent.click(allCheckboxes[0])
|
|
||||||
|
|
||||||
const allCheckboxesChecked = allCheckboxes.filter(c => c.checked)
|
|
||||||
// + 1 because of select all
|
|
||||||
expect(allCheckboxesChecked.length).to.equal(trashedList.length + 1)
|
|
||||||
|
|
||||||
actionsToolbar = screen.getAllByRole('toolbar')[0]
|
|
||||||
})
|
|
||||||
|
|
||||||
it('only shows the download, archive, and restore buttons in top toolbar', function () {
|
|
||||||
expect(screen.queryByLabelText('Trash')).to.be.null
|
|
||||||
within(actionsToolbar).queryByLabelText('Download')
|
|
||||||
within(actionsToolbar).queryByLabelText('Archive')
|
|
||||||
within(actionsToolbar).getByText('Restore') // no icon for this button
|
|
||||||
})
|
|
||||||
|
|
||||||
it('clears selected projects when filter changed', function () {
|
|
||||||
const filterButton = screen.getAllByText('All Projects')[0]
|
|
||||||
fireEvent.click(filterButton)
|
|
||||||
|
|
||||||
const allCheckboxes = screen.getAllByRole<HTMLInputElement>('checkbox')
|
|
||||||
const allCheckboxesChecked = allCheckboxes.filter(c => c.checked)
|
|
||||||
expect(allCheckboxesChecked.length).to.equal(0)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('untrashes all the projects', async function () {
|
|
||||||
fetchMock.delete(`express:/project/:id/trash`, {
|
|
||||||
status: 200,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const untrashButton =
|
it('opens archive modal for all selected projects and archives all', async function () {
|
||||||
within(actionsToolbar).getByText<HTMLInputElement>('Restore')
|
fetchMock.post(
|
||||||
fireEvent.click(untrashButton)
|
`express:/project/${project1Id}/archive`,
|
||||||
|
{
|
||||||
|
status: 200,
|
||||||
|
},
|
||||||
|
{ delay: 0 }
|
||||||
|
)
|
||||||
|
fetchMock.post(
|
||||||
|
`express:/project/${project2Id}/archive`,
|
||||||
|
{
|
||||||
|
status: 200,
|
||||||
|
},
|
||||||
|
{ delay: 0 }
|
||||||
|
)
|
||||||
|
|
||||||
await fetchMock.flush(true)
|
const archiveButton = within(actionsToolbar).getByLabelText('Archive')
|
||||||
expect(fetchMock.done()).to.be.true
|
fireEvent.click(archiveButton)
|
||||||
|
|
||||||
screen.getByText('No projects')
|
const confirmBtn = screen.getByText('Confirm') as HTMLButtonElement
|
||||||
})
|
fireEvent.click(confirmBtn)
|
||||||
|
expect(confirmBtn.disabled).to.be.true
|
||||||
|
|
||||||
it('only untrashes the selected projects', async function () {
|
await fetchMock.flush(true)
|
||||||
// beforeEach selected all, so uncheck the 1st project
|
expect(fetchMock.done()).to.be.true
|
||||||
fireEvent.click(allCheckboxes[1])
|
|
||||||
|
|
||||||
const allCheckboxesChecked = allCheckboxes.filter(c => c.checked)
|
const requests = fetchMock.calls()
|
||||||
expect(allCheckboxesChecked.length).to.equal(trashedList.length - 1)
|
const [projectRequest1Url, projectRequest1Headers] = requests[2]
|
||||||
|
expect(projectRequest1Url).to.equal(`/project/${project1Id}/archive`)
|
||||||
await fetchMock.flush(true)
|
expect(projectRequest1Headers?.method).to.equal('POST')
|
||||||
expect(fetchMock.done()).to.be.true
|
const [projectRequest2Url, projectRequest2Headers] = requests[3]
|
||||||
|
expect(projectRequest2Url).to.equal(`/project/${project2Id}/archive`)
|
||||||
expect(screen.queryByText('No projects')).to.be.null
|
expect(projectRequest2Headers?.method).to.equal('POST')
|
||||||
})
|
|
||||||
|
|
||||||
it('removes project from view when archiving', async function () {
|
|
||||||
fetchMock.post(`express:/project/:id/archive`, {
|
|
||||||
status: 200,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const untrashButton =
|
it('opens trash modal for all selected projects and trashes all', async function () {
|
||||||
within(actionsToolbar).getByLabelText<HTMLInputElement>('Archive')
|
fetchMock.post(
|
||||||
fireEvent.click(untrashButton)
|
`express:/project/${project1Id}/trash`,
|
||||||
|
{
|
||||||
|
status: 200,
|
||||||
|
},
|
||||||
|
{ delay: 0 }
|
||||||
|
)
|
||||||
|
fetchMock.post(
|
||||||
|
`express:/project/${project2Id}/trash`,
|
||||||
|
{
|
||||||
|
status: 200,
|
||||||
|
},
|
||||||
|
{ delay: 0 }
|
||||||
|
)
|
||||||
|
|
||||||
const confirmButton = screen.getByText<HTMLInputElement>('Confirm')
|
const archiveButton = within(actionsToolbar).getByLabelText('Trash')
|
||||||
fireEvent.click(confirmButton)
|
fireEvent.click(archiveButton)
|
||||||
expect(confirmButton.disabled).to.be.true
|
|
||||||
|
|
||||||
await fetchMock.flush(true)
|
const confirmBtn = screen.getByText('Confirm') as HTMLButtonElement
|
||||||
expect(fetchMock.done()).to.be.true
|
fireEvent.click(confirmBtn)
|
||||||
|
expect(confirmBtn.disabled).to.be.true
|
||||||
|
|
||||||
screen.getByText('No projects')
|
await fetchMock.flush(true)
|
||||||
})
|
expect(fetchMock.done()).to.be.true
|
||||||
})
|
|
||||||
|
|
||||||
describe('project tools "More" dropdown', function () {
|
const requests = fetchMock.calls()
|
||||||
beforeEach(async function () {
|
const [projectRequest1Url, projectRequest1Headers] = requests[2]
|
||||||
const filterButton = screen.getAllByText('All Projects')[0]
|
expect(projectRequest1Url).to.equal(`/project/${project1Id}/trash`)
|
||||||
fireEvent.click(filterButton)
|
expect(projectRequest1Headers?.method).to.equal('POST')
|
||||||
allCheckboxes = screen.getAllByRole<HTMLInputElement>('checkbox')
|
const [projectRequest2Url, projectRequest2Headers] = requests[3]
|
||||||
// first one is the select all checkbox
|
expect(projectRequest2Url).to.equal(`/project/${project2Id}/trash`)
|
||||||
fireEvent.click(allCheckboxes[2])
|
expect(projectRequest2Headers?.method).to.equal('POST')
|
||||||
actionsToolbar = screen.getAllByRole('toolbar')[0]
|
|
||||||
})
|
|
||||||
|
|
||||||
it('does not show the dropdown when more than 1 project is selected', async function () {
|
|
||||||
await waitFor(() => {
|
|
||||||
within(actionsToolbar).getByText<HTMLElement>('More')
|
|
||||||
})
|
|
||||||
fireEvent.click(allCheckboxes[0])
|
|
||||||
expect(within(actionsToolbar).queryByText<HTMLElement>('More')).to.be
|
|
||||||
.null
|
|
||||||
})
|
|
||||||
|
|
||||||
it('opens the rename modal, and can rename the project, and view updated', async function () {
|
|
||||||
fetchMock.post(`express:/project/:id/rename`, {
|
|
||||||
status: 200,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
await waitFor(() => {
|
it('only checks the projects that are viewable when there is a load more button', async function () {
|
||||||
const moreDropdown =
|
// first one is the select all checkbox
|
||||||
|
fireEvent.click(allCheckboxes[0])
|
||||||
|
|
||||||
|
allCheckboxes = screen.getAllByRole<HTMLInputElement>('checkbox')
|
||||||
|
let checked = allCheckboxes.filter(c => c.checked)
|
||||||
|
expect(checked.length).to.equal(21) // max projects viewable by default is 20, and plus one for check all
|
||||||
|
|
||||||
|
const loadMoreButton = screen.getByLabelText('Show 17 more projects')
|
||||||
|
fireEvent.click(loadMoreButton)
|
||||||
|
|
||||||
|
allCheckboxes = screen.getAllByRole<HTMLInputElement>('checkbox')
|
||||||
|
expect(allCheckboxes.length).to.equal(currentList.length + 1)
|
||||||
|
checked = allCheckboxes.filter(c => c.checked)
|
||||||
|
expect(checked.length).to.equal(20) // remains same even after showing more
|
||||||
|
})
|
||||||
|
|
||||||
|
it('maintains viewable and selected projects after loading more and then selecting all', async function () {
|
||||||
|
const loadMoreButton = screen.getByLabelText('Show 17 more projects')
|
||||||
|
fireEvent.click(loadMoreButton)
|
||||||
|
// verify button gone
|
||||||
|
screen.getByText(
|
||||||
|
`Showing ${currentList.length} out of ${currentList.length} projects.`
|
||||||
|
)
|
||||||
|
// first one is the select all checkbox
|
||||||
|
fireEvent.click(allCheckboxes[0])
|
||||||
|
// verify button still gone
|
||||||
|
screen.getByText(
|
||||||
|
`Showing ${currentList.length} out of ${currentList.length} projects.`
|
||||||
|
)
|
||||||
|
|
||||||
|
allCheckboxes = screen.getAllByRole<HTMLInputElement>('checkbox')
|
||||||
|
expect(allCheckboxes.length).to.equal(currentList.length + 1)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('archived projects', function () {
|
||||||
|
beforeEach(function () {
|
||||||
|
const filterButton = screen.getAllByText('Archived Projects')[0]
|
||||||
|
fireEvent.click(filterButton)
|
||||||
|
|
||||||
|
allCheckboxes = screen.getAllByRole<HTMLInputElement>('checkbox')
|
||||||
|
expect(allCheckboxes.length === 2).to.be.true
|
||||||
|
// 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 () {
|
||||||
|
beforeEach(function () {
|
||||||
|
const filterButton = screen.getAllByText('Trashed Projects')[0]
|
||||||
|
fireEvent.click(filterButton)
|
||||||
|
|
||||||
|
allCheckboxes = screen.getAllByRole<HTMLInputElement>('checkbox')
|
||||||
|
// + 1 because of select all
|
||||||
|
expect(allCheckboxes.length).to.equal(trashedList.length + 1)
|
||||||
|
|
||||||
|
// first one is the select all checkbox
|
||||||
|
fireEvent.click(allCheckboxes[0])
|
||||||
|
|
||||||
|
const allCheckboxesChecked = allCheckboxes.filter(c => c.checked)
|
||||||
|
// + 1 because of select all
|
||||||
|
expect(allCheckboxesChecked.length).to.equal(trashedList.length + 1)
|
||||||
|
|
||||||
|
actionsToolbar = screen.getAllByRole('toolbar')[0]
|
||||||
|
})
|
||||||
|
|
||||||
|
it('only shows the download, archive, and restore buttons in top toolbar', function () {
|
||||||
|
expect(screen.queryByLabelText('Trash')).to.be.null
|
||||||
|
within(actionsToolbar).queryByLabelText('Download')
|
||||||
|
within(actionsToolbar).queryByLabelText('Archive')
|
||||||
|
within(actionsToolbar).getByText('Restore') // no icon for this button
|
||||||
|
})
|
||||||
|
|
||||||
|
it('clears selected projects when filter changed', function () {
|
||||||
|
const filterButton = screen.getAllByText('All Projects')[0]
|
||||||
|
fireEvent.click(filterButton)
|
||||||
|
|
||||||
|
const allCheckboxes =
|
||||||
|
screen.getAllByRole<HTMLInputElement>('checkbox')
|
||||||
|
const allCheckboxesChecked = allCheckboxes.filter(c => c.checked)
|
||||||
|
expect(allCheckboxesChecked.length).to.equal(0)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('untrashes all the projects', async function () {
|
||||||
|
fetchMock.delete(`express:/project/:id/trash`, {
|
||||||
|
status: 200,
|
||||||
|
})
|
||||||
|
|
||||||
|
const untrashButton =
|
||||||
|
within(actionsToolbar).getByText<HTMLInputElement>('Restore')
|
||||||
|
fireEvent.click(untrashButton)
|
||||||
|
|
||||||
|
await fetchMock.flush(true)
|
||||||
|
expect(fetchMock.done()).to.be.true
|
||||||
|
|
||||||
|
screen.getByText('No projects')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('only untrashes 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(trashedList.length - 1)
|
||||||
|
|
||||||
|
await fetchMock.flush(true)
|
||||||
|
expect(fetchMock.done()).to.be.true
|
||||||
|
|
||||||
|
expect(screen.queryByText('No projects')).to.be.null
|
||||||
|
})
|
||||||
|
|
||||||
|
it('removes project from view when archiving', async function () {
|
||||||
|
fetchMock.post(`express:/project/:id/archive`, {
|
||||||
|
status: 200,
|
||||||
|
})
|
||||||
|
|
||||||
|
const untrashButton =
|
||||||
|
within(actionsToolbar).getByLabelText<HTMLInputElement>('Archive')
|
||||||
|
fireEvent.click(untrashButton)
|
||||||
|
|
||||||
|
const confirmButton = screen.getByText<HTMLInputElement>('Confirm')
|
||||||
|
fireEvent.click(confirmButton)
|
||||||
|
expect(confirmButton.disabled).to.be.true
|
||||||
|
|
||||||
|
await fetchMock.flush(true)
|
||||||
|
expect(fetchMock.done()).to.be.true
|
||||||
|
|
||||||
|
screen.getByText('No projects')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('project tools "More" dropdown', function () {
|
||||||
|
beforeEach(async function () {
|
||||||
|
const filterButton = screen.getAllByText('All Projects')[0]
|
||||||
|
fireEvent.click(filterButton)
|
||||||
|
allCheckboxes = screen.getAllByRole<HTMLInputElement>('checkbox')
|
||||||
|
// first one is the select all checkbox
|
||||||
|
fireEvent.click(allCheckboxes[2])
|
||||||
|
actionsToolbar = screen.getAllByRole('toolbar')[0]
|
||||||
|
})
|
||||||
|
|
||||||
|
it('does not show the dropdown when more than 1 project is selected', async function () {
|
||||||
|
await waitFor(() => {
|
||||||
within(actionsToolbar).getByText<HTMLElement>('More')
|
within(actionsToolbar).getByText<HTMLElement>('More')
|
||||||
fireEvent.click(moreDropdown)
|
})
|
||||||
|
fireEvent.click(allCheckboxes[0])
|
||||||
|
expect(within(actionsToolbar).queryByText<HTMLElement>('More')).to.be
|
||||||
|
.null
|
||||||
})
|
})
|
||||||
|
|
||||||
const renameButton = screen.getByText<HTMLInputElement>('Rename')
|
it('opens the rename modal, and can rename the project, and view updated', async function () {
|
||||||
fireEvent.click(renameButton)
|
fetchMock.post(`express:/project/:id/rename`, {
|
||||||
|
status: 200,
|
||||||
|
})
|
||||||
|
|
||||||
const modal = screen.getAllByRole('dialog')[0]
|
await waitFor(() => {
|
||||||
|
const moreDropdown =
|
||||||
|
within(actionsToolbar).getByText<HTMLElement>('More')
|
||||||
|
fireEvent.click(moreDropdown)
|
||||||
|
})
|
||||||
|
|
||||||
expect(sendSpy).to.be.calledOnce
|
const renameButton = screen.getByText<HTMLInputElement>('Rename')
|
||||||
expect(sendSpy).calledWith('project-list-page-interaction')
|
fireEvent.click(renameButton)
|
||||||
|
|
||||||
// same name
|
const modal = screen.getAllByRole('dialog')[0]
|
||||||
let confirmButton = within(modal).getByText<HTMLInputElement>('Rename')
|
|
||||||
expect(confirmButton.disabled).to.be.true
|
|
||||||
let input = screen.getByLabelText('New Name') as HTMLButtonElement
|
|
||||||
const oldName = input.value
|
|
||||||
|
|
||||||
// no name
|
expect(sendSpy).to.be.calledOnce
|
||||||
let newProjectName = ''
|
expect(sendSpy).calledWith('project-list-page-interaction')
|
||||||
input = screen.getByLabelText('New Name') as HTMLButtonElement
|
|
||||||
fireEvent.change(input, {
|
|
||||||
target: { value: newProjectName },
|
|
||||||
})
|
|
||||||
confirmButton = within(modal).getByText<HTMLInputElement>('Rename')
|
|
||||||
expect(confirmButton.disabled).to.be.true
|
|
||||||
|
|
||||||
// a valid name
|
// same name
|
||||||
newProjectName = 'A new project name'
|
let confirmButton =
|
||||||
input = screen.getByLabelText('New Name') as HTMLButtonElement
|
within(modal).getByText<HTMLInputElement>('Rename')
|
||||||
fireEvent.change(input, {
|
expect(confirmButton.disabled).to.be.true
|
||||||
target: { value: newProjectName },
|
let input = screen.getByLabelText('New Name') as HTMLButtonElement
|
||||||
|
const oldName = input.value
|
||||||
|
|
||||||
|
// no name
|
||||||
|
let newProjectName = ''
|
||||||
|
input = screen.getByLabelText('New Name') as HTMLButtonElement
|
||||||
|
fireEvent.change(input, {
|
||||||
|
target: { value: newProjectName },
|
||||||
|
})
|
||||||
|
confirmButton = within(modal).getByText<HTMLInputElement>('Rename')
|
||||||
|
expect(confirmButton.disabled).to.be.true
|
||||||
|
|
||||||
|
// a valid name
|
||||||
|
newProjectName = 'A new project name'
|
||||||
|
input = screen.getByLabelText('New Name') as HTMLButtonElement
|
||||||
|
fireEvent.change(input, {
|
||||||
|
target: { value: newProjectName },
|
||||||
|
})
|
||||||
|
|
||||||
|
confirmButton = within(modal).getByText<HTMLInputElement>('Rename')
|
||||||
|
expect(confirmButton.disabled).to.be.false
|
||||||
|
fireEvent.click(confirmButton)
|
||||||
|
|
||||||
|
await fetchMock.flush(true)
|
||||||
|
expect(fetchMock.done()).to.be.true
|
||||||
|
|
||||||
|
screen.findByText(newProjectName)
|
||||||
|
expect(screen.queryByText(oldName)).to.be.null
|
||||||
|
|
||||||
|
const allCheckboxes =
|
||||||
|
screen.getAllByRole<HTMLInputElement>('checkbox')
|
||||||
|
const allCheckboxesChecked = allCheckboxes.filter(c => c.checked)
|
||||||
|
expect(allCheckboxesChecked.length).to.equal(0)
|
||||||
})
|
})
|
||||||
|
|
||||||
confirmButton = within(modal).getByText<HTMLInputElement>('Rename')
|
it('opens the copy modal, can copy the project, and view updated', async function () {
|
||||||
expect(confirmButton.disabled).to.be.false
|
const tableRows = screen.getAllByRole('row')
|
||||||
fireEvent.click(confirmButton)
|
const linkForProjectToCopy = within(tableRows[1]).getByRole('link')
|
||||||
|
const projectNameToCopy = linkForProjectToCopy.textContent || '' // needed for type checking
|
||||||
|
screen.findByText(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,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
await fetchMock.flush(true)
|
await waitFor(() => {
|
||||||
expect(fetchMock.done()).to.be.true
|
const moreDropdown =
|
||||||
|
within(actionsToolbar).getByText<HTMLElement>('More')
|
||||||
|
fireEvent.click(moreDropdown)
|
||||||
|
})
|
||||||
|
|
||||||
screen.findByText(newProjectName)
|
const copyButton =
|
||||||
expect(screen.queryByText(oldName)).to.be.null
|
within(actionsToolbar).getByText<HTMLInputElement>('Make a copy')
|
||||||
|
fireEvent.click(copyButton)
|
||||||
|
|
||||||
const allCheckboxes = screen.getAllByRole<HTMLInputElement>('checkbox')
|
// confirm in modal
|
||||||
const allCheckboxesChecked = allCheckboxes.filter(c => c.checked)
|
const copyConfirmButton = document.querySelector(
|
||||||
expect(allCheckboxesChecked.length).to.equal(0)
|
'button[type="submit"]'
|
||||||
|
) as HTMLElement
|
||||||
|
fireEvent.click(copyConfirmButton)
|
||||||
|
|
||||||
|
await fetchMock.flush(true)
|
||||||
|
expect(fetchMock.done()).to.be.true
|
||||||
|
|
||||||
|
expect(sendSpy).to.be.calledOnce
|
||||||
|
expect(sendSpy).calledWith('project-list-page-interaction')
|
||||||
|
|
||||||
|
screen.findByText(copiedProjectName)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('search', function () {
|
||||||
|
it('shows only projects based on the input', async function () {
|
||||||
|
const input = screen.getAllByRole('textbox', {
|
||||||
|
name: /search projects/i,
|
||||||
|
})[0]
|
||||||
|
const value = currentList[0].name
|
||||||
|
|
||||||
|
fireEvent.change(input, { target: { value } })
|
||||||
|
|
||||||
|
const results = screen.getAllByRole('row')
|
||||||
|
expect(results.length).to.equal(2) // first is header
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('copying project', function () {
|
||||||
|
it('correctly updates the view after copying a shared project', async function () {
|
||||||
|
const filterButton = screen.getAllByText('Shared with you')[0]
|
||||||
|
fireEvent.click(filterButton)
|
||||||
|
|
||||||
it('opens the copy modal, can copy the project, and view updated', async function () {
|
|
||||||
const tableRows = screen.getAllByRole('row')
|
const tableRows = screen.getAllByRole('row')
|
||||||
|
|
||||||
const linkForProjectToCopy = within(tableRows[1]).getByRole('link')
|
const linkForProjectToCopy = within(tableRows[1]).getByRole('link')
|
||||||
const projectNameToCopy = linkForProjectToCopy.textContent || '' // needed for type checking
|
const projectNameToCopy = linkForProjectToCopy.textContent
|
||||||
screen.findByText(projectNameToCopy) // make sure not just empty string
|
const copiedProjectName = `${projectNameToCopy} Copy`
|
||||||
const copiedProjectName = `${projectNameToCopy} (Copy)`
|
|
||||||
fetchMock.post(`express:/project/:id/clone`, {
|
fetchMock.post(`express:/project/:id/clone`, {
|
||||||
status: 200,
|
status: 200,
|
||||||
body: {
|
body: {
|
||||||
|
@ -419,15 +521,7 @@ describe('<ProjectListRoot />', function () {
|
||||||
archived: false,
|
archived: false,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
const copyButton = within(tableRows[1]).getAllByLabelText('Copy')[0]
|
||||||
await waitFor(() => {
|
|
||||||
const moreDropdown =
|
|
||||||
within(actionsToolbar).getByText<HTMLElement>('More')
|
|
||||||
fireEvent.click(moreDropdown)
|
|
||||||
})
|
|
||||||
|
|
||||||
const copyButton =
|
|
||||||
within(actionsToolbar).getByText<HTMLInputElement>('Make a copy')
|
|
||||||
fireEvent.click(copyButton)
|
fireEvent.click(copyButton)
|
||||||
|
|
||||||
// confirm in modal
|
// confirm in modal
|
||||||
|
@ -442,71 +536,20 @@ describe('<ProjectListRoot />', function () {
|
||||||
expect(sendSpy).to.be.calledOnce
|
expect(sendSpy).to.be.calledOnce
|
||||||
expect(sendSpy).calledWith('project-list-page-interaction')
|
expect(sendSpy).calledWith('project-list-page-interaction')
|
||||||
|
|
||||||
|
expect(screen.queryByText(copiedProjectName)).to.be.null
|
||||||
|
|
||||||
|
const yourProjectFilter = screen.getAllByText('Your Projects')[0]
|
||||||
|
fireEvent.click(yourProjectFilter)
|
||||||
screen.findByText(copiedProjectName)
|
screen.findByText(copiedProjectName)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
|
||||||
|
|
||||||
describe('search', function () {
|
describe('notifications', function () {
|
||||||
it('shows only projects based on the input', async function () {
|
it('email confirmation alert is displayed', async function () {
|
||||||
const input = screen.getAllByRole('textbox', {
|
screen.getByText(
|
||||||
name: /search projects/i,
|
'Please confirm your email test@overleaf.com by clicking on the link in the confirmation email'
|
||||||
})[0]
|
)
|
||||||
const value = currentList[0].name
|
|
||||||
|
|
||||||
fireEvent.change(input, { target: { value } })
|
|
||||||
|
|
||||||
const results = screen.getAllByRole('row')
|
|
||||||
expect(results.length).to.equal(2) // first is header
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('copying project', function () {
|
|
||||||
it('correctly updates the view after copying a shared project', async function () {
|
|
||||||
const filterButton = screen.getAllByText('Shared with you')[0]
|
|
||||||
fireEvent.click(filterButton)
|
|
||||||
|
|
||||||
const tableRows = screen.getAllByRole('row')
|
|
||||||
|
|
||||||
const linkForProjectToCopy = within(tableRows[1]).getByRole('link')
|
|
||||||
const projectNameToCopy = linkForProjectToCopy.textContent
|
|
||||||
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 copyButton = within(tableRows[1]).getAllByLabelText('Copy')[0]
|
|
||||||
fireEvent.click(copyButton)
|
|
||||||
|
|
||||||
// confirm in modal
|
|
||||||
const copyConfirmButton = document.querySelector(
|
|
||||||
'button[type="submit"]'
|
|
||||||
) as HTMLElement
|
|
||||||
fireEvent.click(copyConfirmButton)
|
|
||||||
|
|
||||||
await fetchMock.flush(true)
|
|
||||||
expect(fetchMock.done()).to.be.true
|
|
||||||
|
|
||||||
expect(sendSpy).to.be.calledOnce
|
|
||||||
expect(sendSpy).calledWith('project-list-page-interaction')
|
|
||||||
|
|
||||||
expect(screen.queryByText(copiedProjectName)).to.be.null
|
|
||||||
|
|
||||||
const yourProjectFilter = screen.getAllByText('Your Projects')[0]
|
|
||||||
fireEvent.click(yourProjectFilter)
|
|
||||||
screen.findByText(copiedProjectName)
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -7,6 +7,8 @@ import { renderWithProjectListContext } from '../../helpers/render-with-context'
|
||||||
|
|
||||||
describe('<TagsList />', function () {
|
describe('<TagsList />', function () {
|
||||||
beforeEach(async function () {
|
beforeEach(async function () {
|
||||||
|
global.localStorage.clear()
|
||||||
|
window.metaAttributesCache = new Map()
|
||||||
window.metaAttributesCache.set('ol-tags', [
|
window.metaAttributesCache.set('ol-tags', [
|
||||||
{
|
{
|
||||||
_id: 'abc123def456',
|
_id: 'abc123def456',
|
||||||
|
@ -30,6 +32,7 @@ describe('<TagsList />', function () {
|
||||||
|
|
||||||
renderWithProjectListContext(<TagsList />)
|
renderWithProjectListContext(<TagsList />)
|
||||||
|
|
||||||
|
await fetchMock.flush(true)
|
||||||
await waitFor(() => expect(fetchMock.called('/api/project')))
|
await waitFor(() => expect(fetchMock.called('/api/project')))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -37,7 +40,7 @@ describe('<TagsList />', function () {
|
||||||
fetchMock.reset()
|
fetchMock.reset()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('displays the tags list', async function () {
|
it('displays the tags list', function () {
|
||||||
screen.getByRole('heading', {
|
screen.getByRole('heading', {
|
||||||
name: 'Tags/Folders',
|
name: 'Tags/Folders',
|
||||||
})
|
})
|
||||||
|
|
Loading…
Reference in a new issue