Merge pull request #23940 from overleaf/td-react-18

Upgrade to React 18

GitOrigin-RevId: 9b81936e6eea2bccd97fe5c2c5841f0b946371b8
This commit is contained in:
Tim Down
2025-05-02 08:05:29 +00:00
committed by Copybot
parent 4464320757
commit 7abafb01ea
213 changed files with 2469 additions and 2105 deletions
@@ -3,23 +3,16 @@ import { expect } from 'chai'
import fetchMock from 'fetch-mock'
import sinon from 'sinon'
import ModalContentNewProjectForm from '../../../../../../frontend/js/features/project-list/components/new-project-button/modal-content-new-project-form'
import * as useLocationModule from '../../../../../../frontend/js/shared/hooks/use-location'
import { location } from '@/shared/components/location'
describe('<ModalContentNewProjectForm />', function () {
let assignStub: sinon.SinonStub
beforeEach(function () {
assignStub = sinon.stub()
this.locationStub = sinon.stub(useLocationModule, 'useLocation').returns({
assign: assignStub,
replace: sinon.stub(),
reload: sinon.stub(),
setHash: sinon.stub(),
})
this.locationWrapperSandbox = sinon.createSandbox()
this.locationWrapperStub = this.locationWrapperSandbox.stub(location)
})
afterEach(function () {
this.locationStub.restore()
this.locationWrapperSandbox.restore()
fetchMock.removeRoutes().clearHistory()
})
@@ -51,10 +44,11 @@ describe('<ModalContentNewProjectForm />', function () {
expect(newProjectMock.callHistory.called()).to.be.true
const assignStub = this.locationWrapperStub.assign
await waitFor(() => {
sinon.assert.calledOnce(assignStub)
sinon.assert.calledWith(assignStub, `/project/${projectId}`)
})
sinon.assert.calledWith(assignStub, `/project/${projectId}`)
})
it('shows error when project name contains "/"', async function () {
@@ -41,7 +41,7 @@ import { Project } from '../../../../../types/project/dashboard/api'
import GroupsAndEnterpriseBanner from '../../../../../frontend/js/features/project-list/components/notifications/groups-and-enterprise-banner'
import GroupSsoSetupSuccess from '../../../../../frontend/js/features/project-list/components/notifications/groups/group-sso-setup-success'
import localStorage from '@/infrastructure/local-storage'
import * as useLocationModule from '../../../../../frontend/js/shared/hooks/use-location'
import { location } from '@/shared/components/location'
import {
commonsSubscription,
freeSubscription,
@@ -50,7 +50,9 @@ import {
} from '../fixtures/user-subscriptions'
import getMeta from '@/utils/meta'
const renderWithinProjectListProvider = (Component: React.ComponentType) => {
const renderWithinProjectListProvider = (
Component: React.ComponentType<React.PropsWithChildren>
) => {
render(<Component />, {
wrapper: ({ children }) => (
<ProjectListProvider>
@@ -655,12 +657,10 @@ describe('<UserNotifications />', function () {
window.metaAttributesCache.set('ol-user', {
signUpDate: new Date('2024-01-01').toISOString(),
})
this.clock = sinon.useFakeTimers(new Date('2025-07-01').getTime())
})
afterEach(function () {
fetchMock.removeRoutes().clearHistory()
this.clock.restore()
})
function testUnconfirmedNotification(
@@ -675,7 +675,9 @@ describe('<UserNotifications />', function () {
fetchMock.post('/user/emails/resend_confirmation', 200)
const email = userEmails[0].email
const notificationBody = screen.getByTestId('pro-notification-body')
const notificationBody = await screen.findByTestId(
'pro-notification-body'
)
if (isPrimary) {
expect(notificationBody.textContent).to.contain(
@@ -691,7 +693,7 @@ describe('<UserNotifications />', function () {
fireEvent.click(resendButton)
await waitForElementToBeRemoved(() =>
screen.getByRole('button', { name: /resend/i })
screen.queryByRole('button', { name: /resend/i })
)
expect(fetchMock.callHistory.called()).to.be.true
@@ -717,7 +719,7 @@ describe('<UserNotifications />', function () {
fetchMock.post('/user/emails/resend_confirmation', 200)
const email = untrustedUserData.email
const notificationBody = screen.getByTestId(
const notificationBody = await screen.findByTestId(
'not-trusted-notification-body'
)
expect(notificationBody.textContent).to.contain(
@@ -742,7 +744,9 @@ describe('<UserNotifications />', function () {
await fetchMock.callHistory.flush(true)
fetchMock.post('/user/emails/resend_confirmation', 500)
const resendButtons = screen.getAllByRole('button', { name: /resend/i })
const resendButtons = await screen.findAllByRole('button', {
name: /resend/i,
})
const resendButton = resendButtons[0]
fireEvent.click(resendButton)
const notificationBody = screen.getByTestId('pro-notification-body')
@@ -767,7 +771,7 @@ describe('<UserNotifications />', function () {
renderWithinProjectListProvider(ConfirmEmail)
await fetchMock.callHistory.flush(true)
const alert = screen.getByRole('alert')
const alert = await screen.findByRole('alert')
const email = unconfirmedCommonsUserData.email
const notificationBody = within(alert).getByTestId('notification-body')
expect(notificationBody.textContent).to.contain(
@@ -788,7 +792,7 @@ describe('<UserNotifications />', function () {
renderWithinProjectListProvider(ConfirmEmail)
await fetchMock.callHistory.flush(true)
const alert = screen.getByRole('alert')
const alert = await screen.findByRole('alert')
const email = unconfirmedCommonsUserData.email
const notificationBody = within(alert).getByTestId(
'pro-notification-body'
@@ -808,22 +812,15 @@ describe('<UserNotifications />', function () {
})
describe('<Affiliation/>', function () {
let assignStub: sinon.SinonStub
beforeEach(function () {
Object.assign(getMeta('ol-ExposedSettings'), exposedSettings)
assignStub = sinon.stub()
this.locationStub = sinon.stub(useLocationModule, 'useLocation').returns({
assign: assignStub,
replace: sinon.stub(),
reload: sinon.stub(),
setHash: sinon.stub(),
})
this.locationWrapperSandbox = sinon.createSandbox()
this.locationWrapperStub = this.locationWrapperSandbox.stub(location)
fetchMock.removeRoutes().clearHistory()
})
afterEach(function () {
this.locationStub.restore()
this.locationWrapperSandbox.restore()
fetchMock.removeRoutes().clearHistory()
})
@@ -879,9 +876,9 @@ describe('<UserNotifications />', function () {
fireEvent.click(
screen.getByRole('button', { name: /confirm affiliation/i })
)
sinon.assert.calledOnce(assignStub)
sinon.assert.calledOnce(this.locationWrapperStub.assign)
sinon.assert.calledWithMatch(
assignStub,
this.locationWrapperStub.assign,
`${exposedSettings.samlInitPath}?university_id=${professionalUserData.affiliation.institution.id}&reconfirm=/project`
)
})
@@ -944,8 +941,7 @@ describe('<UserNotifications />', function () {
renderWithinProjectListProvider(GroupsAndEnterpriseBanner)
await fetchMock.callHistory.flush(true)
expect(screen.queryByRole('button', { name: 'Contact Sales' })).to.not.be
.null
await screen.findByRole('button', { name: 'Contact Sales' })
})
it('shows the banner for users that have dismissed the banner more than 30 days ago', async function () {
@@ -960,8 +956,7 @@ describe('<UserNotifications />', function () {
renderWithinProjectListProvider(GroupsAndEnterpriseBanner)
await fetchMock.callHistory.flush(true)
expect(screen.queryByRole('button', { name: 'Contact Sales' })).to.not.be
.null
await screen.findByRole('button', { name: 'Contact Sales' })
})
it('does not show the banner for users that have dismissed the banner within the last 30 days', async function () {
@@ -1014,7 +1009,7 @@ describe('<UserNotifications />', function () {
renderWithinProjectListProvider(GroupsAndEnterpriseBanner)
await fetchMock.callHistory.flush(true)
screen.getByText(
await screen.findByText(
'Overleaf On-Premises: Does your company want to keep its data within its firewall? Overleaf offers Server Pro, an on-premises solution for companies. Get in touch to learn more.'
)
const link = screen.getByRole('button', { name: 'Contact Sales' })
@@ -1031,7 +1026,7 @@ describe('<UserNotifications />', function () {
renderWithinProjectListProvider(GroupsAndEnterpriseBanner)
await fetchMock.callHistory.flush(true)
screen.getByText(
await screen.findByText(
'Why do Fortune 500 companies and top research institutions trust Overleaf to streamline their collaboration? Get in touch to learn more.'
)
const link = screen.getByRole('button', { name: 'Contact Sales' })
@@ -19,7 +19,7 @@ import {
archiveableProject,
copyableProject,
} from '../fixtures/projects-data'
import * as useLocationModule from '../../../../../frontend/js/shared/hooks/use-location'
import { location } from '@/shared/components/location'
import getMeta from '@/utils/meta'
const {
@@ -37,7 +37,6 @@ describe('<ProjectListRoot />', function () {
this.timeout('10s')
let sendMBSpy: sinon.SinonSpy
let assignStub: sinon.SinonStub
beforeEach(async function () {
global.localStorage.clear()
@@ -68,19 +67,14 @@ describe('<ProjectListRoot />', function () {
window.metaAttributesCache.set('ol-navbar', {
items: [],
})
assignStub = sinon.stub()
this.locationStub = sinon.stub(useLocationModule, 'useLocation').returns({
assign: assignStub,
replace: sinon.stub(),
reload: sinon.stub(),
setHash: sinon.stub(),
})
this.locationWrapperSandbox = sinon.createSandbox()
this.locationWrapperStub = this.locationWrapperSandbox.stub(location)
})
afterEach(function () {
sendMBSpy.restore()
fetchMock.removeRoutes().clearHistory()
this.locationStub.restore()
this.locationWrapperSandbox.restore()
})
describe('welcome page', function () {
@@ -136,6 +130,7 @@ describe('<ProjectListRoot />', function () {
within(actionsToolbar).getByLabelText('Download')
fireEvent.click(downloadButton)
const assignStub = this.locationWrapperStub.assign
await waitFor(() => {
expect(assignStub).to.have.been.called
})
@@ -430,12 +425,15 @@ describe('<ProjectListRoot />', function () {
{ repeat: leavableList.length }
)
allCheckboxes = screen.getAllByRole<HTMLInputElement>('checkbox')
// + 1 because of select all
expect(allCheckboxes.length).to.equal(leavableList.length + 1)
await waitFor(() =>
expect(
screen.getAllByRole<HTMLInputElement>('checkbox').length
).to.equal(leavableList.length + 1)
)
// first one is the select all checkbox
fireEvent.click(allCheckboxes[0])
fireEvent.click(screen.getAllByRole('checkbox')[0])
actionsToolbar = screen.getAllByRole('toolbar')[0]
@@ -491,9 +489,11 @@ describe('<ProjectListRoot />', function () {
{ repeat: deletableList.length }
)
allCheckboxes = screen.getAllByRole<HTMLInputElement>('checkbox')
// + 1 because of select all
expect(allCheckboxes.length).to.equal(deletableList.length + 1)
await waitFor(() => {
allCheckboxes = screen.getAllByRole<HTMLInputElement>('checkbox')
// + 1 because of select all
expect(allCheckboxes.length).to.equal(deletableList.length + 1)
})
// first one is the select all checkbox
fireEvent.click(allCheckboxes[0])
@@ -563,14 +563,15 @@ describe('<ProjectListRoot />', function () {
{ repeat: leavableList.length }
)
allCheckboxes = screen.getAllByRole<HTMLInputElement>('checkbox')
// + 1 because of select all
expect(allCheckboxes.length).to.equal(
deletableAndLeavableList.length + 1
await waitFor(() =>
expect(
screen.getAllByRole<HTMLInputElement>('checkbox').length
).to.equal(deletableAndLeavableList.length + 1)
)
// first one is the select all checkbox
fireEvent.click(allCheckboxes[0])
fireEvent.click(screen.getAllByRole<HTMLInputElement>('checkbox')[0])
actionsToolbar = screen.getAllByRole('toolbar')[0]
@@ -663,9 +664,11 @@ describe('<ProjectListRoot />', function () {
)
).to.be.true
})
expect(
screen.queryAllByText(projectsData[index].name)
).to.have.length(0)
await waitFor(() => {
expect(
screen.queryAllByText(projectsData[index].name)
).to.have.length(0)
})
screen.getAllByRole('button', {
name: `${this.tagName} (${--visibleProjectsCount})`,
@@ -699,13 +702,15 @@ describe('<ProjectListRoot />', function () {
status: 204,
})
await waitFor(() => {
const tagsDropdown = within(actionsToolbar).getByLabelText('Tags')
fireEvent.click(tagsDropdown)
})
const tagsDropdown =
await within(actionsToolbar).findByLabelText('Tags')
fireEvent.click(tagsDropdown)
screen.getByText('Add to tag')
const newTagButton = screen.getByText('Create new tag')
const newTagButton = screen.getByRole('menuitem', {
name: 'Create new tag',
})
fireEvent.click(newTagButton)
const modal = screen.getAllByRole('dialog')[0]
@@ -731,7 +736,11 @@ describe('<ProjectListRoot />', function () {
})
).to.be.true
screen.getByRole('button', { name: `${this.newTagName} (2)` })
await screen.findByRole(
'button',
{ name: `${this.newTagName} (2)` },
{ timeout: 5000 }
)
})
it('opens the tags dropdown and remove a tag from selected projects', async function () {
@@ -949,7 +958,7 @@ describe('<ProjectListRoot />', function () {
).to.be.true
const table = await screen.findByRole('table')
within(table).getByText(newProjectName)
await within(table).findByText(newProjectName)
expect(within(table).queryByText(oldName)).to.be.null
const allCheckboxesInTable =
@@ -1025,7 +1034,7 @@ describe('<ProjectListRoot />', function () {
}
)
screen.getByText(copiedProjectName)
await screen.findByText(copiedProjectName)
})
})
})
@@ -1073,6 +1082,8 @@ describe('<ProjectListRoot />', function () {
}
beforeEach(function () {
fetchMock.removeRoutes().clearHistory()
allCheckboxes = screen.getAllByRole<HTMLInputElement>('checkbox')
// first one is the select all checkbox, just check 2 at first
fireEvent.click(allCheckboxes[1])
@@ -30,7 +30,7 @@ describe('Add affiliation widget', function () {
await fetchMock.callHistory.flush(true)
await waitFor(() => expect(fetchMock.callHistory.called('/api/project')))
screen.getByText(/are you affiliated with an institution/i)
await screen.findByText(/are you affiliated with an institution/i)
const addAffiliationLink = screen.getByRole('button', {
name: /add affiliation/i,
})
@@ -63,7 +63,7 @@ describe('<TagsList />', function () {
const tag1Button = screen.getByText('Tag 1')
assert.isFalse(tag1Button.closest('li')?.classList.contains('active'))
await fireEvent.click(tag1Button)
fireEvent.click(tag1Button)
assert.isTrue(tag1Button.closest('li')?.classList.contains('active'))
})
@@ -85,7 +85,7 @@ describe('<TagsList />', function () {
name: 'New Tag',
})
await fireEvent.click(newTagButton)
fireEvent.click(newTagButton)
})
it('modal is open', async function () {
@@ -97,7 +97,7 @@ describe('<TagsList />', function () {
const modal = screen.getAllByRole('dialog', { hidden: false })[0]
const cancelButton = within(modal).getByRole('button', { name: 'Cancel' })
await fireEvent.click(cancelButton)
fireEvent.click(cancelButton)
expect(screen.queryByRole('dialog', { hidden: false })).to.be.null
})
@@ -144,13 +144,15 @@ describe('<TagsList />', function () {
const createButton = within(modal).getByRole('button', { name: 'Create' })
expect(createButton.hasAttribute('disabled')).to.be.false
await fireEvent.click(createButton)
fireEvent.click(createButton)
await waitFor(
() => expect(fetchMock.callHistory.called(`/tag`)).to.be.true
)
expect(screen.queryByRole('dialog', { hidden: false })).to.be.null
await waitFor(
() => expect(screen.queryByRole('dialog', { hidden: false })).to.be.null
)
screen.getByRole('button', {
name: 'New Tag (0)',
@@ -164,9 +166,9 @@ describe('<TagsList />', function () {
const dropdownToggle = within(
tag1Button.closest('li') as HTMLElement
).getByTestId('tag-dropdown-toggle')
await fireEvent.click(dropdownToggle)
fireEvent.click(dropdownToggle)
const editMenuItem = await screen.findByRole('menuitem', { name: 'Edit' })
await fireEvent.click(editMenuItem)
fireEvent.click(editMenuItem)
})
it('modal is open', async function () {
@@ -178,7 +180,7 @@ describe('<TagsList />', function () {
const modal = screen.getAllByRole('dialog', { hidden: false })[0]
const cancelButton = within(modal).getByRole('button', { name: 'Cancel' })
await fireEvent.click(cancelButton)
fireEvent.click(cancelButton)
expect(screen.queryByRole('dialog', { hidden: false })).to.be.null
})
@@ -237,13 +239,15 @@ describe('<TagsList />', function () {
const saveButton = within(modal).getByRole('button', { name: 'Save' })
expect(saveButton.hasAttribute('disabled')).to.be.false
await fireEvent.click(saveButton)
fireEvent.click(saveButton)
await waitFor(() =>
expect(fetchMock.callHistory.called(`/tag/abc123def456/rename`))
)
expect(screen.queryByRole('dialog', { hidden: false })).to.be.null
await waitFor(
() => expect(screen.queryByRole('dialog', { hidden: false })).to.be.null
)
screen.getByRole('button', {
name: 'New Tag Name (1)',
@@ -257,11 +261,11 @@ describe('<TagsList />', function () {
const dropdownToggle = within(
tag1Button.closest('li') as HTMLElement
).getByTestId('tag-dropdown-toggle')
await fireEvent.click(dropdownToggle)
fireEvent.click(dropdownToggle)
const deleteMenuItem = await screen.findByRole('menuitem', {
name: 'Delete',
})
await fireEvent.click(deleteMenuItem)
fireEvent.click(deleteMenuItem)
})
it('modal is open', async function () {
@@ -273,20 +277,22 @@ describe('<TagsList />', function () {
const modal = screen.getAllByRole('dialog', { hidden: false })[0]
const cancelButton = within(modal).getByRole('button', { name: 'Cancel' })
await fireEvent.click(cancelButton)
fireEvent.click(cancelButton)
expect(screen.queryByRole('dialog', { hidden: false })).to.be.null
})
it('clicking Delete sends a request', async function () {
const modal = screen.getAllByRole('dialog', { hidden: false })[0]
const deleteButton = within(modal).getByRole('button', { name: 'Delete' })
await fireEvent.click(deleteButton)
fireEvent.click(deleteButton)
await waitFor(() =>
expect(fetchMock.callHistory.called(`/tag/bcd234efg567`))
)
expect(screen.queryByRole('dialog', { hidden: false })).to.be.null
await waitFor(
() => expect(screen.queryByRole('dialog', { hidden: false })).to.be.null
)
expect(
screen.queryByRole('button', {
name: 'Another Tag (2)',
@@ -299,13 +305,13 @@ describe('<TagsList />', function () {
const modal = screen.getAllByRole('dialog', { hidden: false })[0]
const deleteButton = within(modal).getByRole('button', { name: 'Delete' })
await fireEvent.click(deleteButton)
fireEvent.click(deleteButton)
await waitFor(() =>
expect(fetchMock.callHistory.called(`/tag/bcd234efg567`))
)
within(modal).getByText('Sorry, something went wrong')
await within(modal).findByText('Sorry, something went wrong')
})
})
})
@@ -24,7 +24,7 @@ describe('<SystemMessages />', function () {
await fetchMock.callHistory.flush(true)
screen.getByText(data.content)
await screen.findByText(data.content)
expect(screen.queryByRole('button', { name: /close/i })).to.be.null
})
@@ -38,7 +38,7 @@ describe('<SystemMessages />', function () {
await fetchMock.callHistory.flush(true)
screen.getByText(data.content)
await screen.findByText(data.content)
const closeBtn = screen.getByRole('button', { name: /close/i })
fireEvent.click(closeBtn)
@@ -2,32 +2,25 @@ import { expect } from 'chai'
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
import sinon from 'sinon'
import { projectsData } from '../../../../fixtures/projects-data'
import * as useLocationModule from '../../../../../../../../frontend/js/shared/hooks/use-location'
import { location } from '@/shared/components/location'
import { CompileAndDownloadProjectPDFButtonTooltip } from '../../../../../../../../frontend/js/features/project-list/components/table/cells/action-buttons/compile-and-download-project-pdf-button'
import fetchMock from 'fetch-mock'
import * as eventTracking from '@/infrastructure/event-tracking'
describe('<CompileAndDownloadProjectPDFButton />', function () {
let assignStub: sinon.SinonStub
let locationStub: sinon.SinonStub
let sendMBSpy: sinon.SinonSpy
beforeEach(function () {
sendMBSpy = sinon.spy(eventTracking, 'sendMB')
assignStub = sinon.stub()
locationStub = sinon.stub(useLocationModule, 'useLocation').returns({
assign: assignStub,
replace: sinon.stub(),
reload: sinon.stub(),
setHash: sinon.stub(),
})
this.locationWrapperSandbox = sinon.createSandbox()
this.locationWrapperStub = this.locationWrapperSandbox.stub(location)
render(
<CompileAndDownloadProjectPDFButtonTooltip project={projectsData[0]} />
)
})
afterEach(function () {
locationStub.restore()
this.locationWrapperSandbox.restore()
fetchMock.removeRoutes().clearHistory()
sendMBSpy.restore()
})
@@ -57,6 +50,7 @@ describe('<CompileAndDownloadProjectPDFButton />', function () {
screen.getByRole('button', { name: 'Compiling…' })
})
const assignStub = this.locationWrapperStub.assign
await waitFor(() => {
expect(assignStub).to.have.been.called
})
@@ -85,9 +79,9 @@ describe('<CompileAndDownloadProjectPDFButton />', function () {
}) as HTMLButtonElement
fireEvent.click(btn)
await waitFor(() => {
screen.getByText(`${projectsData[0].name}: PDF unavailable for download`)
})
expect(assignStub).to.have.not.been.called
await screen.findByText(
`${projectsData[0].name}: PDF unavailable for download`
)
expect(this.locationWrapperStub.assign).to.have.not.been.called
})
})
@@ -3,24 +3,17 @@ import { fireEvent, render, screen, waitFor } from '@testing-library/react'
import sinon from 'sinon'
import { DownloadProjectButtonTooltip } from '../../../../../../../../frontend/js/features/project-list/components/table/cells/action-buttons/download-project-button'
import { projectsData } from '../../../../fixtures/projects-data'
import * as useLocationModule from '../../../../../../../../frontend/js/shared/hooks/use-location'
import { location } from '@/shared/components/location'
describe('<DownloadProjectButton />', function () {
let assignStub: sinon.SinonStub
beforeEach(function () {
assignStub = sinon.stub()
this.locationStub = sinon.stub(useLocationModule, 'useLocation').returns({
assign: assignStub,
replace: sinon.stub(),
reload: sinon.stub(),
setHash: sinon.stub(),
})
this.locationWrapperSandbox = sinon.createSandbox()
this.locationWrapperStub = this.locationWrapperSandbox.stub(location)
render(<DownloadProjectButtonTooltip project={projectsData[0]} />)
})
afterEach(function () {
this.locationStub.restore()
this.locationWrapperSandbox.restore()
})
it('renders tooltip for button', async function () {
@@ -35,6 +28,7 @@ describe('<DownloadProjectButton />', function () {
}) as HTMLButtonElement
fireEvent.click(btn)
const assignStub = this.locationWrapperStub.assign
await waitFor(() => {
expect(assignStub).to.have.been.called
})
@@ -57,7 +57,7 @@ describe('<InlineTags />', function () {
const removeButton = screen.getByRole('button', {
name: 'Remove tag My Test Tag',
})
await fireEvent.click(removeButton)
fireEvent.click(removeButton)
await waitFor(() =>
expect(
fetchMock.callHistory.called(
@@ -1,4 +1,4 @@
import { screen, within, fireEvent } from '@testing-library/react'
import { screen, within, fireEvent, waitFor } from '@testing-library/react'
import { expect } from 'chai'
import ProjectListTable from '../../../../../../frontend/js/features/project-list/components/table/project-list-table'
import { currentProjects } from '../../fixtures/projects-data'
@@ -67,9 +67,13 @@ describe('<ProjectListTable />', function () {
renderWithProjectListContext(<ProjectListTable />)
await fetchMock.callHistory.flush(true)
await waitFor(() =>
expect(screen.getAllByRole('row').length).to.equal(
currentProjects.length + 1
)
)
const rows = screen.getAllByRole('row')
rows.shift() // remove first row since it's the header
expect(rows.length).to.equal(currentProjects.length)
// Project name cell
currentProjects.forEach(project => {
@@ -139,42 +143,60 @@ describe('<ProjectListTable />', function () {
it('selects all projects when header checkbox checked', async function () {
renderWithProjectListContext(<ProjectListTable />)
await fetchMock.callHistory.flush(true)
const checkbox = screen.getByLabelText('Select all projects')
const checkbox = await screen.findByLabelText('Select all projects')
fireEvent.click(checkbox)
const allCheckboxes = screen.getAllByRole<HTMLInputElement>('checkbox')
const allCheckboxesChecked = allCheckboxes.filter(c => c.checked)
// + 1 because of select all checkbox
expect(allCheckboxesChecked.length).to.equal(currentProjects.length + 1)
await waitFor(() => {
const allCheckboxes = screen.queryAllByRole<HTMLInputElement>('checkbox')
const allCheckboxesChecked = allCheckboxes.filter(c => c.checked)
// + 1 because of select all checkbox
expect(allCheckboxesChecked.length).to.equal(currentProjects.length + 1)
})
})
it('unselects all projects when select all checkbox uchecked', async function () {
renderWithProjectListContext(<ProjectListTable />)
await fetchMock.callHistory.flush(true)
const checkbox = screen.getByLabelText('Select all projects')
const checkbox = await screen.findByLabelText('Select all projects')
fireEvent.click(checkbox)
fireEvent.click(checkbox)
const allCheckboxes = screen.getAllByRole<HTMLInputElement>('checkbox')
const allCheckboxesChecked = allCheckboxes.filter(c => c.checked)
expect(allCheckboxesChecked.length).to.equal(0)
await waitFor(() => {
const allCheckboxes = screen.queryAllByRole<HTMLInputElement>('checkbox')
const allCheckboxesChecked = allCheckboxes.filter(c => c.checked)
expect(allCheckboxesChecked.length).to.equal(0)
})
})
it('unselects select all projects checkbox when one project is unchecked', async function () {
renderWithProjectListContext(<ProjectListTable />)
await fetchMock.callHistory.flush(true)
const checkbox = screen.getByLabelText('Select all projects')
const checkbox = await screen.findByLabelText('Select all projects')
fireEvent.click(checkbox)
let allCheckboxes = screen.getAllByRole<HTMLInputElement>('checkbox')
expect(allCheckboxes[1].getAttribute('data-project-id')).to.exist // make sure we are unchecking a project checkbox
fireEvent.click(allCheckboxes[1])
allCheckboxes = screen.getAllByRole<HTMLInputElement>('checkbox')
const allCheckboxesChecked = allCheckboxes.filter(c => c.checked)
expect(allCheckboxesChecked.length).to.equal(currentProjects.length - 1)
await waitFor(() => {
expect(
screen
.getAllByRole<HTMLInputElement>('checkbox')[1]
.getAttribute('data-project-id')
).to.exist // make sure we are unchecking a project checkbox
})
fireEvent.click(screen.getAllByRole<HTMLInputElement>('checkbox')[1])
await waitFor(() => {
const allCheckboxes = screen.getAllByRole<HTMLInputElement>('checkbox')
const allCheckboxesChecked = allCheckboxes.filter(c => c.checked)
expect(allCheckboxesChecked.length).to.equal(currentProjects.length - 1)
})
})
it('only checks the checked project', async function () {
renderWithProjectListContext(<ProjectListTable />)
await fetchMock.callHistory.flush(true)
const checkbox = screen.getByLabelText(`Select ${currentProjects[0].name}`)
const checkbox = await screen.findByLabelText(
`Select ${currentProjects[0].name}`
)
fireEvent.click(checkbox)
const allCheckboxes = screen.getAllByRole<HTMLInputElement>('checkbox')
const allCheckboxesChecked = allCheckboxes.filter(c => c.checked)
@@ -1,4 +1,4 @@
import { render, screen, within } from '@testing-library/react'
import { render, screen, waitFor, within } from '@testing-library/react'
import { expect } from 'chai'
import moment from 'moment/moment'
import fetchMock from 'fetch-mock'
@@ -68,24 +68,30 @@ describe('<ProjectTools />', function () {
fetchMock.removeRoutes().clearHistory()
})
it('does not show the Rename option for a project owned by a different user', function () {
it('does not show the Rename option for a project owned by a different user', async function () {
render(<ProjectListRootInner />)
screen.getByLabelText('Select Starfleet Report (readAndWrite)').click()
screen.getByRole('button', { name: 'More' }).click()
expect(
within(
screen.getByTestId('project-tools-more-dropdown-menu')
).queryByRole('menuitem', { name: 'Rename' })
).to.be.null
await waitFor(
() =>
expect(
within(
screen.getByTestId('project-tools-more-dropdown-menu')
).queryByRole('menuitem', { name: 'Rename' })
).to.be.null
)
})
it('displays the Rename option for a project owned by the current user', function () {
it('displays the Rename option for a project owned by the current user', async function () {
render(<ProjectListRootInner />)
screen.getByLabelText('Select Starfleet Report (owner)').click()
screen.getByRole('button', { name: 'More' }).click()
within(screen.getByTestId('project-tools-more-dropdown-menu'))
.getByRole('menuitem', { name: 'Rename' })
.click()
within(screen.getByRole('dialog')).getByText('Rename Project')
const menu = await screen.findByTestId('project-tools-more-dropdown-menu')
const menuItem = within(menu).getByRole('menuitem', {
name: 'Rename',
})
menuItem.click()
const dialog = await screen.findByRole('dialog')
within(dialog).getByText('Rename Project')
})
})
@@ -92,6 +92,6 @@ describe('<RenameProjectModal />', function () {
await waitFor(() => expect(postRenameMock.callHistory.called()).to.be.true)
screen.getByText('Something went wrong. Please try again.')
await screen.findByText('Something went wrong. Please try again.')
})
})