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:
Alexandre Bourdin 2022-10-12 16:17:09 +02:00 committed by Copybot
parent dce00bbefe
commit debe76baa6
4 changed files with 438 additions and 389 deletions

View file

@ -5,7 +5,6 @@ import Notification from '../notification'
import Icon from '../../../../../shared/components/icon'
import getMeta from '../../../../../utils/meta'
import useAsyncDismiss from '../hooks/useAsyncDismiss'
import { useProjectListContext } from '../../../context/project-list-context'
import useAsync from '../../../../../shared/hooks/use-async'
import { FetchError, postJSON } from '../../../../../infrastructure/fetch-json'
import { ExposedSettings } from '../../../../../../../types/exposed-settings'
@ -14,7 +13,6 @@ import { User } from '../../../../../../../types/user'
function Common() {
const { t } = useTranslation()
const { totalProjectsCount } = useProjectListContext()
const { samlInitPath } = getMeta('ol-ExposedSettings') as ExposedSettings
const notifications = getMeta('ol-notifications', []) as NotificationType[]
const user = getMeta('ol-user', []) as Pick<User, 'features'>
@ -33,7 +31,7 @@ function Common() {
).catch(console.error)
}
if (!totalProjectsCount || !notifications.length) {
if (!notifications.length) {
return null
}

View file

@ -135,6 +135,11 @@ function ProjectListPageContent() {
mdOffset={2}
className="project-list-empty-col"
>
<Row>
<Col xs={12}>
<UserNotifications />
</Col>
</Row>
<WelcomeMessage />
</Col>
</Row>

View file

@ -24,20 +24,17 @@ describe('<ProjectListRoot />', function () {
sendSpy = sinon.spy(eventTracking, 'send')
window.metaAttributesCache = new Map()
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
Object.defineProperty(window, 'location', {
value: { assign: locationStub },
})
renderWithProjectListContext(<ProjectListRoot />, {
projects: fullList,
})
await fetchMock.flush(true)
await waitFor(() => {
screen.findByRole('table')
})
})
afterEach(function () {
@ -49,360 +46,465 @@ describe('<ProjectListRoot />', function () {
})
})
describe('checkboxes', function () {
let allCheckboxes: Array<HTMLInputElement> = []
let actionsToolbar: HTMLElement
let project1Id: string | null, project2Id: string | null
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]
describe('welcome page', function () {
beforeEach(async function () {
renderWithProjectListContext(<ProjectListRoot />, {
projects: [],
})
await fetchMock.flush(true)
})
it('downloads all selected projects and then unselects them', async function () {
const downloadButton = within(actionsToolbar).getByLabelText('Download')
fireEvent.click(downloadButton)
it('the welcome page is displayed', async function () {
screen.getByRole('heading', { name: 'Welcome to Overleaf!' })
})
await waitFor(() => {
expect(locationStub).to.have.been.called
})
sinon.assert.calledWithMatch(
locationStub,
`/project/download/zip?project_ids=${project1Id},${project2Id}`
it('the email confirmation alert is not displayed', async function () {
expect(
screen.queryByText(
'Please confirm your email test@overleaf.com by clicking on the link in the confirmation email'
)
).to.be.null
})
})
const allCheckboxes = screen.getAllByRole<HTMLInputElement>('checkbox')
const allCheckboxesChecked = allCheckboxes.filter(c => c.checked)
expect(allCheckboxesChecked.length).to.equal(0)
describe('project table', function () {
beforeEach(async function () {
renderWithProjectListContext(<ProjectListRoot />, {
projects: fullList,
})
it('opens archive modal for all selected projects and archives all', async function () {
fetchMock.post(
`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)
await fetchMock.flush(true)
await waitFor(() => {
screen.findByRole('table')
})
})
describe('archived projects', function () {
beforeEach(function () {
const filterButton = screen.getAllByText('Archived Projects')[0]
fireEvent.click(filterButton)
describe('checkboxes', function () {
let allCheckboxes: Array<HTMLInputElement> = []
let actionsToolbar: HTMLElement
let project1Id: string | null, project2Id: string | null
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')
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])
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,
project1Id = allCheckboxes[1].getAttribute('data-project-id')
project2Id = allCheckboxes[2].getAttribute('data-project-id')
actionsToolbar = screen.getAllByRole('toolbar')[0]
})
const unarchiveButton =
within(actionsToolbar).getByText<HTMLInputElement>('Restore')
fireEvent.click(unarchiveButton)
it('downloads all selected projects and then unselects them', async function () {
const downloadButton =
within(actionsToolbar).getByLabelText('Download')
fireEvent.click(downloadButton)
await fetchMock.flush(true)
expect(fetchMock.done()).to.be.true
await waitFor(() => {
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 () {
// 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 allCheckboxes =
screen.getAllByRole<HTMLInputElement>('checkbox')
const allCheckboxesChecked = allCheckboxes.filter(c => c.checked)
expect(allCheckboxesChecked.length).to.equal(0)
})
const untrashButton =
within(actionsToolbar).getByText<HTMLInputElement>('Restore')
fireEvent.click(untrashButton)
it('opens archive modal for all selected projects and archives all', async function () {
fetchMock.post(
`express:/project/${project1Id}/archive`,
{
status: 200,
},
{ delay: 0 }
)
fetchMock.post(
`express:/project/${project2Id}/archive`,
{
status: 200,
},
{ delay: 0 }
)
await fetchMock.flush(true)
expect(fetchMock.done()).to.be.true
const archiveButton = within(actionsToolbar).getByLabelText('Archive')
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 () {
// beforeEach selected all, so uncheck the 1st project
fireEvent.click(allCheckboxes[1])
await fetchMock.flush(true)
expect(fetchMock.done()).to.be.true
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 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')
})
const untrashButton =
within(actionsToolbar).getByLabelText<HTMLInputElement>('Archive')
fireEvent.click(untrashButton)
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 confirmButton = screen.getByText<HTMLInputElement>('Confirm')
fireEvent.click(confirmButton)
expect(confirmButton.disabled).to.be.true
const archiveButton = within(actionsToolbar).getByLabelText('Trash')
fireEvent.click(archiveButton)
await fetchMock.flush(true)
expect(fetchMock.done()).to.be.true
const confirmBtn = screen.getByText('Confirm') as HTMLButtonElement
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 () {
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')
})
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,
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(() => {
const moreDropdown =
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 () {
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')
fireEvent.click(moreDropdown)
})
fireEvent.click(allCheckboxes[0])
expect(within(actionsToolbar).queryByText<HTMLElement>('More')).to.be
.null
})
const renameButton = screen.getByText<HTMLInputElement>('Rename')
fireEvent.click(renameButton)
it('opens the rename modal, and can rename the project, and view updated', async function () {
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
expect(sendSpy).calledWith('project-list-page-interaction')
const renameButton = screen.getByText<HTMLInputElement>('Rename')
fireEvent.click(renameButton)
// same name
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
const modal = screen.getAllByRole('dialog')[0]
// 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
expect(sendSpy).to.be.calledOnce
expect(sendSpy).calledWith('project-list-page-interaction')
// a valid name
newProjectName = 'A new project name'
input = screen.getByLabelText('New Name') as HTMLButtonElement
fireEvent.change(input, {
target: { value: newProjectName },
// same name
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
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')
expect(confirmButton.disabled).to.be.false
fireEvent.click(confirmButton)
it('opens the copy modal, can copy the project, and view updated', async 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
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)
expect(fetchMock.done()).to.be.true
await waitFor(() => {
const moreDropdown =
within(actionsToolbar).getByText<HTMLElement>('More')
fireEvent.click(moreDropdown)
})
screen.findByText(newProjectName)
expect(screen.queryByText(oldName)).to.be.null
const copyButton =
within(actionsToolbar).getByText<HTMLInputElement>('Make a copy')
fireEvent.click(copyButton)
const allCheckboxes = screen.getAllByRole<HTMLInputElement>('checkbox')
const allCheckboxesChecked = allCheckboxes.filter(c => c.checked)
expect(allCheckboxesChecked.length).to.equal(0)
// 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')
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 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)`
const projectNameToCopy = linkForProjectToCopy.textContent
const copiedProjectName = `${projectNameToCopy} Copy`
fetchMock.post(`express:/project/:id/clone`, {
status: 200,
body: {
@ -419,15 +521,7 @@ describe('<ProjectListRoot />', function () {
archived: false,
},
})
await waitFor(() => {
const moreDropdown =
within(actionsToolbar).getByText<HTMLElement>('More')
fireEvent.click(moreDropdown)
})
const copyButton =
within(actionsToolbar).getByText<HTMLInputElement>('Make a copy')
const copyButton = within(tableRows[1]).getAllByLabelText('Copy')[0]
fireEvent.click(copyButton)
// confirm in modal
@ -442,71 +536,20 @@ describe('<ProjectListRoot />', function () {
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)
})
})
})
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)
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,
},
describe('notifications', function () {
it('email confirmation alert is displayed', async function () {
screen.getByText(
'Please confirm your email test@overleaf.com by clicking on the link in the confirmation email'
)
})
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)
})
})
})

View file

@ -7,6 +7,8 @@ import { renderWithProjectListContext } from '../../helpers/render-with-context'
describe('<TagsList />', function () {
beforeEach(async function () {
global.localStorage.clear()
window.metaAttributesCache = new Map()
window.metaAttributesCache.set('ol-tags', [
{
_id: 'abc123def456',
@ -30,6 +32,7 @@ describe('<TagsList />', function () {
renderWithProjectListContext(<TagsList />)
await fetchMock.flush(true)
await waitFor(() => expect(fetchMock.called('/api/project')))
})
@ -37,7 +40,7 @@ describe('<TagsList />', function () {
fetchMock.reset()
})
it('displays the tags list', async function () {
it('displays the tags list', function () {
screen.getByRole('heading', {
name: 'Tags/Folders',
})