Files
overleaf-cep/services/web/test/frontend/features/ide-settings/settings/project-notifications-setting.test.tsx
T
Copilot cb3fbaba84 [web] update notification settings copy for mentions (#35953)
GitOrigin-RevId: cc86f0931f6740f1d509118ee558719bc3559816
2026-09-17 11:43:53 +00:00

360 lines
10 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { screen, render, waitFor } from '@testing-library/react'
import { expect } from 'chai'
import fetchMock from 'fetch-mock'
import userEvent from '@testing-library/user-event'
import { EditorProviders, PROJECT_ID } from '../../../helpers/editor-providers'
import { SettingsModalProvider } from '@/features/ide-settings/context/settings-modal-context'
import ProjectNotificationsSetting from '@/features/ide-settings/components/editor-settings/project-notifications-setting'
const preferencesUrl = `/notifications/preferences/project/${PROJECT_ID}`
const allNotificationsOn = {
commentOnOwnProject: true,
commentOnInvitedProject: true,
repliesOnAuthoredThread: true,
repliesOnParticipatingThread: true,
mentionsInThread: true,
commentResolvedOnAuthoredThread: true,
commentResolvedOnParticipatingThread: true,
commentReopenedOnAuthoredThread: true,
commentReopenedOnParticipatingThread: true,
trackedChangesOnOwnProject: true,
trackedChangesOnInvitedProject: true,
trackChangesAcceptedOnAuthoredChange: true,
trackChangesRejectedOnAuthoredChange: true,
}
const repliesOnlyPreferences = {
commentOnOwnProject: false,
commentOnInvitedProject: false,
repliesOnAuthoredThread: true,
repliesOnParticipatingThread: true,
mentionsInThread: true,
commentResolvedOnAuthoredThread: true,
commentResolvedOnParticipatingThread: true,
commentReopenedOnAuthoredThread: true,
commentReopenedOnParticipatingThread: true,
trackedChangesOnOwnProject: false,
trackedChangesOnInvitedProject: false,
trackChangesAcceptedOnAuthoredChange: true,
trackChangesRejectedOnAuthoredChange: true,
}
const globallyMutedPreferences = {
trackedChangesOnOwnProject: false,
trackedChangesOnInvitedProject: false,
commentOnOwnProject: false,
commentOnInvitedProject: false,
repliesOnAuthoredThread: false,
repliesOnParticipatingThread: false,
muteAllNotifications: true,
commentResolvedOnAuthoredThread: false,
commentResolvedOnParticipatingThread: false,
commentReopenedOnAuthoredThread: false,
commentReopenedOnParticipatingThread: false,
trackChangesAcceptedOnAuthoredChange: false,
trackChangesRejectedOnAuthoredChange: false,
}
const allNotificationsOff = {
commentOnOwnProject: false,
commentOnInvitedProject: false,
repliesOnAuthoredThread: false,
repliesOnParticipatingThread: false,
mentionsInThread: false,
commentResolvedOnAuthoredThread: false,
commentResolvedOnParticipatingThread: false,
commentReopenedOnAuthoredThread: false,
commentReopenedOnParticipatingThread: false,
trackedChangesOnOwnProject: false,
trackedChangesOnInvitedProject: false,
trackChangesAcceptedOnAuthoredChange: false,
trackChangesRejectedOnAuthoredChange: false,
}
const defaultPreferences = {
trackedChangesOnOwnProject: true,
trackedChangesOnInvitedProject: false,
commentOnOwnProject: true,
commentOnInvitedProject: false,
repliesOnAuthoredThread: true,
repliesOnParticipatingThread: true,
muteAllNotifications: false,
commentResolvedOnAuthoredThread: true,
commentResolvedOnParticipatingThread: true,
commentReopenedOnAuthoredThread: true,
commentReopenedOnParticipatingThread: true,
trackChangesAcceptedOnAuthoredChange: true,
}
function renderComponent(props: { permissionsLevel?: string } = {}) {
return render(
<EditorProviders permissionsLevel={props.permissionsLevel as any}>
<SettingsModalProvider>
<ProjectNotificationsSetting />
</SettingsModalProvider>
</EditorProviders>
)
}
describe('<ProjectNotificationsSetting />', function () {
afterEach(function () {
fetchMock.removeRoutes().clearHistory()
window.metaAttributesCache.delete('ol-splitTestVariants')
})
it('shows loading indicator while preferences are loading', async function () {
fetchMock.get(preferencesUrl, new Promise(() => {}))
renderComponent()
expect(await screen.findByRole('status')).to.exist
expect(screen.queryByLabelText('All project activity', { exact: false })).to
.not.exist
})
it('selects "All project activity" when all notifications are on', async function () {
fetchMock.get(preferencesUrl, allNotificationsOn)
renderComponent()
await waitFor(
() =>
expect(
(
screen.getByLabelText('All project activity', {
exact: false,
}) as HTMLInputElement
).checked
).to.be.true
)
expect(
(
screen.getByLabelText('Replies to your activity only', {
exact: false,
}) as HTMLInputElement
).checked
).to.be.false
expect(
(screen.getByLabelText('Off', { exact: false }) as HTMLInputElement)
.checked
).to.be.false
})
it('selects "Replies to your activity only" when only reply notifications are on', async function () {
fetchMock.get(preferencesUrl, repliesOnlyPreferences)
renderComponent()
await waitFor(
() =>
expect(
(
screen.getByLabelText('Replies to your activity only', {
exact: false,
}) as HTMLInputElement
).checked
).to.be.true
)
expect(
(
screen.getByLabelText('All project activity', {
exact: false,
}) as HTMLInputElement
).checked
).to.be.false
expect(
(screen.getByLabelText('Off', { exact: false }) as HTMLInputElement)
.checked
).to.be.false
})
it('shows mentions copy when comment-mentions is enabled', async function () {
window.metaAttributesCache.set('ol-splitTestVariants', {
'comment-mentions': 'enabled',
})
fetchMock.get(preferencesUrl, repliesOnlyPreferences)
renderComponent()
await waitFor(
() =>
expect(
screen.getByLabelText('Mentions and replies only', { exact: false })
).to.exist
)
expect(
screen.getByText(
'You’ll be notified about @mentions, replies and activity on your track changes.',
{ exact: false }
)
).to.exist
})
it('selects "Off" when all notifications are off', async function () {
fetchMock.get(preferencesUrl, allNotificationsOff)
renderComponent()
await waitFor(
() =>
expect(
(screen.getByLabelText('Off', { exact: false }) as HTMLInputElement)
.checked
).to.be.true
)
expect(
(
screen.getByLabelText('All project activity', {
exact: false,
}) as HTMLInputElement
).checked
).to.be.false
expect(
(
screen.getByLabelText('Replies to your activity only', {
exact: false,
}) as HTMLInputElement
).checked
).to.be.false
})
it('shows muted message and hides radio buttons when muteAllNotifications is true', async function () {
fetchMock.get(preferencesUrl, globallyMutedPreferences)
renderComponent()
await waitFor(
() =>
expect(
screen.getByText(
'You are not receiving any notifications, as you have disabled all project notifications.',
{ exact: false }
)
).to.exist
)
expect(
screen.getByRole('link', { name: 'Change settings' }).getAttribute('href')
).to.equal('/user/notification-preferences')
expect(screen.queryByLabelText('All project activity', { exact: false })).to
.not.exist
expect(
screen.queryByLabelText('Replies to your activity only', { exact: false })
).to.not.exist
expect(screen.queryByLabelText('Off', { exact: false })).to.not.exist
})
it('POSTs "replies" preferences when "Replies to your activity only" is selected', async function () {
fetchMock.get(preferencesUrl, allNotificationsOn)
const saveMock = fetchMock.post(preferencesUrl, { status: 200 })
renderComponent()
await waitFor(
() =>
expect(
(
screen.getByLabelText('All project activity', {
exact: false,
}) as HTMLInputElement
).checked
).to.be.true
)
await userEvent.click(
screen.getByLabelText('Replies to your activity only', { exact: false })
)
expect(
saveMock.callHistory.called(preferencesUrl, {
body: repliesOnlyPreferences,
})
).to.be.true
})
it('POSTs "off" preferences when "Off" is selected', async function () {
fetchMock.get(preferencesUrl, allNotificationsOn)
const saveMock = fetchMock.post(preferencesUrl, { status: 200 })
renderComponent()
await waitFor(
() =>
expect(
(
screen.getByLabelText('All project activity', {
exact: false,
}) as HTMLInputElement
).checked
).to.be.true
)
await userEvent.click(screen.getByLabelText('Off', { exact: false }))
expect(
saveMock.callHistory.called(preferencesUrl, {
body: allNotificationsOff,
})
).to.be.true
})
it('shows "all" for owner with default preferences', async function () {
fetchMock.get(preferencesUrl, defaultPreferences)
renderComponent({ permissionsLevel: 'owner' })
await waitFor(
() =>
expect(
(
screen.getByLabelText('All project activity', {
exact: false,
}) as HTMLInputElement
).checked
).to.be.true
)
})
it('shows "replies" for invitee with default preferences', async function () {
fetchMock.get(preferencesUrl, defaultPreferences)
renderComponent({ permissionsLevel: 'readAndWrite' })
await waitFor(
() =>
expect(
(
screen.getByLabelText('Replies to your activity only', {
exact: false,
}) as HTMLInputElement
).checked
).to.be.true
)
})
it('POSTs "all" preferences when "All project activity" is selected', async function () {
fetchMock.get(preferencesUrl, allNotificationsOff)
const saveMock = fetchMock.post(preferencesUrl, { status: 200 })
renderComponent()
await waitFor(
() =>
expect(
(screen.getByLabelText('Off', { exact: false }) as HTMLInputElement)
.checked
).to.be.true
)
await userEvent.click(
screen.getByLabelText('All project activity', { exact: false })
)
expect(
saveMock.callHistory.called(preferencesUrl, {
body: allNotificationsOn,
})
).to.be.true
})
})