|
|
@@ -0,0 +1,643 @@
|
|
|
+import { expect } from 'chai'
|
|
|
+import sinon from 'sinon'
|
|
|
+import {
|
|
|
+ fireEvent,
|
|
|
+ render,
|
|
|
+ screen,
|
|
|
+ waitForElementToBeRemoved,
|
|
|
+} from '@testing-library/react'
|
|
|
+import fetchMock from 'fetch-mock'
|
|
|
+import { merge, cloneDeep } from 'lodash'
|
|
|
+import {
|
|
|
+ professionalUserData,
|
|
|
+ unconfirmedUserData,
|
|
|
+} from '../../settings/fixtures/test-user-email-data'
|
|
|
+import {
|
|
|
+ notification,
|
|
|
+ notificationsInstitution,
|
|
|
+} from '../fixtures/notifications-data'
|
|
|
+import Common from '../../../../../frontend/js/features/project-list/components/notifications/groups/common'
|
|
|
+import Institution from '../../../../../frontend/js/features/project-list/components/notifications/groups/institution'
|
|
|
+import ConfirmEmail from '../../../../../frontend/js/features/project-list/components/notifications/groups/confirm-email'
|
|
|
+import ReconfirmationInfo from '../../../../../frontend/js/features/project-list/components/notifications/groups/affiliation/reconfirmation-info'
|
|
|
+import { ProjectListProvider } from '../../../../../frontend/js/features/project-list/context/project-list-context'
|
|
|
+import {
|
|
|
+ Notification,
|
|
|
+ Institution as InstitutionType,
|
|
|
+} from '../../../../../types/project/dashboard/notification'
|
|
|
+import { DeepPartial } from '../../../../../types/utils'
|
|
|
+import { Project } from '../../../../../types/project/dashboard/api'
|
|
|
+
|
|
|
+const renderWithinProjectListProvider = (Component: React.ComponentType) => {
|
|
|
+ render(<Component />, {
|
|
|
+ wrapper: ({ children }) => (
|
|
|
+ <ProjectListProvider>
|
|
|
+ <ul className="list-unstyled">{children}</ul>
|
|
|
+ </ProjectListProvider>
|
|
|
+ ),
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+describe('<UserNotifications />', function () {
|
|
|
+ const exposedSettings = {
|
|
|
+ samlInitPath: '/fakeSaml/',
|
|
|
+ appName: 'Overleaf',
|
|
|
+ }
|
|
|
+
|
|
|
+ beforeEach(function () {
|
|
|
+ window.metaAttributesCache = window.metaAttributesCache || new Map()
|
|
|
+ fetchMock.reset()
|
|
|
+
|
|
|
+ // at least one project is required to show some notifications
|
|
|
+ const projects = [{}] as Project[]
|
|
|
+ fetchMock.post(/\/api\/project/, {
|
|
|
+ status: 200,
|
|
|
+ body: {
|
|
|
+ projects,
|
|
|
+ totalSize: projects.length,
|
|
|
+ },
|
|
|
+ })
|
|
|
+ })
|
|
|
+
|
|
|
+ afterEach(function () {
|
|
|
+ window.metaAttributesCache = new Map()
|
|
|
+ fetchMock.reset()
|
|
|
+ })
|
|
|
+
|
|
|
+ describe('<Common>', function () {
|
|
|
+ beforeEach(function () {
|
|
|
+ window.metaAttributesCache = window.metaAttributesCache || new Map()
|
|
|
+ window.metaAttributesCache.set('ol-ExposedSettings', exposedSettings)
|
|
|
+ })
|
|
|
+
|
|
|
+ afterEach(function () {
|
|
|
+ window.metaAttributesCache = new Map()
|
|
|
+ fetchMock.reset()
|
|
|
+ })
|
|
|
+
|
|
|
+ it('accepts project invite', async function () {
|
|
|
+ const reconfiguredNotification: DeepPartial<Notification> = {
|
|
|
+ _id: 1,
|
|
|
+ templateKey: 'notification_project_invite',
|
|
|
+ }
|
|
|
+ window.metaAttributesCache.set('ol-notifications', [
|
|
|
+ merge(cloneDeep(notification), reconfiguredNotification),
|
|
|
+ ])
|
|
|
+
|
|
|
+ renderWithinProjectListProvider(Common)
|
|
|
+ await fetchMock.flush(true)
|
|
|
+
|
|
|
+ const deleteMock = fetchMock.delete(
|
|
|
+ `/notifications/${reconfiguredNotification._id}`,
|
|
|
+ 200
|
|
|
+ )
|
|
|
+ const acceptMock = fetchMock.post(
|
|
|
+ `project/${notification.messageOpts.projectId}/invite/token/${notification.messageOpts.token}/accept`,
|
|
|
+ 200
|
|
|
+ )
|
|
|
+
|
|
|
+ screen.getByRole('alert')
|
|
|
+ screen.getByText(/would like you to join/i)
|
|
|
+
|
|
|
+ const joinBtn = screen.getByRole('button', {
|
|
|
+ name: /join project/i,
|
|
|
+ }) as HTMLButtonElement
|
|
|
+
|
|
|
+ expect(joinBtn.disabled).to.be.false
|
|
|
+
|
|
|
+ fireEvent.click(joinBtn)
|
|
|
+
|
|
|
+ expect(joinBtn.disabled).to.be.true
|
|
|
+
|
|
|
+ await waitForElementToBeRemoved(() =>
|
|
|
+ screen.getByRole('button', { name: /joining/i })
|
|
|
+ )
|
|
|
+
|
|
|
+ expect(acceptMock.called()).to.be.true
|
|
|
+ screen.getByText(/joined/i)
|
|
|
+ expect(screen.queryByRole('button', { name: /join project/i })).to.be.null
|
|
|
+
|
|
|
+ const openProject = screen.getByRole('link', { name: /open project/i })
|
|
|
+ expect(openProject.getAttribute('href')).to.equal(
|
|
|
+ `/project/${notification.messageOpts.projectId}`
|
|
|
+ )
|
|
|
+
|
|
|
+ const closeBtn = screen.getByRole('button', { name: /close/i })
|
|
|
+ fireEvent.click(closeBtn)
|
|
|
+
|
|
|
+ expect(deleteMock.called()).to.be.true
|
|
|
+ expect(screen.queryByRole('alert')).to.be.null
|
|
|
+ })
|
|
|
+
|
|
|
+ it('fails to accept project invite', async function () {
|
|
|
+ const reconfiguredNotification: DeepPartial<Notification> = {
|
|
|
+ _id: 1,
|
|
|
+ templateKey: 'notification_project_invite',
|
|
|
+ }
|
|
|
+ window.metaAttributesCache.set('ol-notifications', [
|
|
|
+ merge(cloneDeep(notification), reconfiguredNotification),
|
|
|
+ ])
|
|
|
+
|
|
|
+ renderWithinProjectListProvider(Common)
|
|
|
+ await fetchMock.flush(true)
|
|
|
+ fetchMock.post(
|
|
|
+ `project/${notification.messageOpts.projectId}/invite/token/${notification.messageOpts.token}/accept`,
|
|
|
+ 500
|
|
|
+ )
|
|
|
+
|
|
|
+ screen.getByRole('alert')
|
|
|
+ screen.getByText(/would like you to join/i)
|
|
|
+
|
|
|
+ const joinBtn = screen.getByRole('button', {
|
|
|
+ name: /join project/i,
|
|
|
+ }) as HTMLButtonElement
|
|
|
+
|
|
|
+ fireEvent.click(joinBtn)
|
|
|
+
|
|
|
+ await waitForElementToBeRemoved(() =>
|
|
|
+ screen.getByRole('button', { name: /joining/i })
|
|
|
+ )
|
|
|
+
|
|
|
+ expect(fetchMock.called()).to.be.true
|
|
|
+ screen.getByRole('button', { name: /join project/i })
|
|
|
+ expect(screen.queryByRole('link', { name: /open project/i })).to.be.null
|
|
|
+ })
|
|
|
+
|
|
|
+ it('shows WFH2020', async function () {
|
|
|
+ const reconfiguredNotification: DeepPartial<Notification> = {
|
|
|
+ _id: 1,
|
|
|
+ templateKey: 'wfh_2020_upgrade_offer',
|
|
|
+ }
|
|
|
+ window.metaAttributesCache.set('ol-notifications', [
|
|
|
+ merge(cloneDeep(notification), reconfiguredNotification),
|
|
|
+ ])
|
|
|
+
|
|
|
+ renderWithinProjectListProvider(Common)
|
|
|
+ await fetchMock.flush(true)
|
|
|
+ fetchMock.delete(`/notifications/${reconfiguredNotification._id}`, 200)
|
|
|
+
|
|
|
+ screen.getByRole('alert')
|
|
|
+ screen.getByText(/your free WFH2020 upgrade came to an end on/i)
|
|
|
+
|
|
|
+ const viewLink = screen.getByRole('link', { name: /view/i })
|
|
|
+ expect(viewLink.getAttribute('href')).to.equal(
|
|
|
+ 'https://www.overleaf.com/events/wfh2020'
|
|
|
+ )
|
|
|
+ const closeBtn = screen.getByRole('button', { name: /close/i })
|
|
|
+ fireEvent.click(closeBtn)
|
|
|
+
|
|
|
+ expect(fetchMock.called()).to.be.true
|
|
|
+ expect(screen.queryByRole('alert')).to.be.null
|
|
|
+ })
|
|
|
+
|
|
|
+ it('shows IP matched affiliation with SSO enabled', async function () {
|
|
|
+ const reconfiguredNotification: DeepPartial<Notification> = {
|
|
|
+ _id: 1,
|
|
|
+ templateKey: 'notification_ip_matched_affiliation',
|
|
|
+ messageOpts: { ssoEnabled: true },
|
|
|
+ }
|
|
|
+ window.metaAttributesCache.set('ol-notifications', [
|
|
|
+ merge(cloneDeep(notification), reconfiguredNotification),
|
|
|
+ ])
|
|
|
+
|
|
|
+ renderWithinProjectListProvider(Common)
|
|
|
+ await fetchMock.flush(true)
|
|
|
+ fetchMock.delete(`/notifications/${reconfiguredNotification._id}`, 200)
|
|
|
+
|
|
|
+ screen.getByRole('alert')
|
|
|
+ screen.getByText(/it looks like you’re at/i)
|
|
|
+ screen.getByText(/you can now log in through your institution/i)
|
|
|
+ screen.getByText(
|
|
|
+ /link an institutional email address to your account to get started/i
|
|
|
+ )
|
|
|
+
|
|
|
+ const findOutMore = screen.getByRole('link', { name: /find out more/i })
|
|
|
+ expect(findOutMore.getAttribute('href')).to.equal(
|
|
|
+ 'https://www.overleaf.com/learn/how-to/Institutional_Login'
|
|
|
+ )
|
|
|
+ const linkAccount = screen.getByRole('link', { name: /link account/i })
|
|
|
+ expect(linkAccount.getAttribute('href')).to.equal(
|
|
|
+ `${exposedSettings.samlInitPath}?university_id=${notification.messageOpts.institutionId}&auto=/project`
|
|
|
+ )
|
|
|
+ const closeBtn = screen.getByRole('button', { name: /close/i })
|
|
|
+ fireEvent.click(closeBtn)
|
|
|
+
|
|
|
+ expect(fetchMock.called()).to.be.true
|
|
|
+ expect(screen.queryByRole('alert')).to.be.null
|
|
|
+ })
|
|
|
+
|
|
|
+ it('shows IP matched affiliation with SSO disabled', async function () {
|
|
|
+ const reconfiguredNotification: DeepPartial<Notification> = {
|
|
|
+ _id: 1,
|
|
|
+ templateKey: 'notification_ip_matched_affiliation',
|
|
|
+ messageOpts: { ssoEnabled: false },
|
|
|
+ }
|
|
|
+ window.metaAttributesCache.set('ol-notifications', [
|
|
|
+ merge(cloneDeep(notification), reconfiguredNotification),
|
|
|
+ ])
|
|
|
+
|
|
|
+ renderWithinProjectListProvider(Common)
|
|
|
+ await fetchMock.flush(true)
|
|
|
+ fetchMock.delete(`/notifications/${reconfiguredNotification._id}`, 200)
|
|
|
+
|
|
|
+ screen.getByRole('alert')
|
|
|
+ screen.getByText(/it looks like you’re at/i)
|
|
|
+ screen.getByText(/did you know that/i)
|
|
|
+ screen.getByText(
|
|
|
+ /add an institutional email address to claim your features/i
|
|
|
+ )
|
|
|
+
|
|
|
+ const addAffiliation = screen.getByRole('link', {
|
|
|
+ name: /add affiliation/i,
|
|
|
+ })
|
|
|
+ expect(addAffiliation.getAttribute('href')).to.equal(`/user/settings`)
|
|
|
+ const closeBtn = screen.getByRole('button', { name: /close/i })
|
|
|
+ fireEvent.click(closeBtn)
|
|
|
+
|
|
|
+ expect(fetchMock.called()).to.be.true
|
|
|
+ expect(screen.queryByRole('alert')).to.be.null
|
|
|
+ })
|
|
|
+
|
|
|
+ it('shows tpds file limit', async function () {
|
|
|
+ const reconfiguredNotification: DeepPartial<Notification> = {
|
|
|
+ templateKey: 'notification_tpds_file_limit',
|
|
|
+ }
|
|
|
+ window.metaAttributesCache.set('ol-notifications', [
|
|
|
+ merge(cloneDeep(notification), reconfiguredNotification),
|
|
|
+ ])
|
|
|
+
|
|
|
+ renderWithinProjectListProvider(Common)
|
|
|
+ await fetchMock.flush(true)
|
|
|
+
|
|
|
+ screen.getByRole('alert')
|
|
|
+ screen.getByText(/file limit/i)
|
|
|
+ screen.getByText(
|
|
|
+ /please decrease the size of your project to prevent further errors/i
|
|
|
+ )
|
|
|
+
|
|
|
+ const accountSettings = screen.getByRole('link', {
|
|
|
+ name: /account settings/i,
|
|
|
+ })
|
|
|
+ expect(accountSettings.getAttribute('href')).to.equal('/user/settings')
|
|
|
+ const closeBtn = screen.getByRole('button', { name: /close/i })
|
|
|
+ fireEvent.click(closeBtn)
|
|
|
+
|
|
|
+ expect(screen.queryByRole('alert')).to.be.null
|
|
|
+ })
|
|
|
+
|
|
|
+ it('shows dropbox duplicate project names warning', async function () {
|
|
|
+ const reconfiguredNotification: DeepPartial<Notification> = {
|
|
|
+ _id: 1,
|
|
|
+ templateKey: 'notification_dropbox_duplicate_project_names',
|
|
|
+ }
|
|
|
+ window.metaAttributesCache.set('ol-notifications', [
|
|
|
+ merge(cloneDeep(notification), reconfiguredNotification),
|
|
|
+ ])
|
|
|
+
|
|
|
+ renderWithinProjectListProvider(Common)
|
|
|
+ await fetchMock.flush(true)
|
|
|
+ fetchMock.delete(`/notifications/${reconfiguredNotification._id}`, 200)
|
|
|
+
|
|
|
+ screen.getByRole('alert')
|
|
|
+ screen.getByText(/you have more than one project called/i)
|
|
|
+ screen.getByText(/make your project names unique/i)
|
|
|
+
|
|
|
+ const learnMore = screen.getByRole('link', { name: /learn more/i })
|
|
|
+ expect(learnMore.getAttribute('href')).to.equal(
|
|
|
+ '/learn/how-to/Dropbox_Synchronization#Troubleshooting'
|
|
|
+ )
|
|
|
+ const closeBtn = screen.getByRole('button', { name: /close/i })
|
|
|
+ fireEvent.click(closeBtn)
|
|
|
+
|
|
|
+ expect(fetchMock.called()).to.be.true
|
|
|
+ expect(screen.queryByRole('alert')).to.be.null
|
|
|
+ })
|
|
|
+
|
|
|
+ it('shows dropbox unlinked tue to lapsed reconfirmation', async function () {
|
|
|
+ const reconfiguredNotification: DeepPartial<Notification> = {
|
|
|
+ _id: 1,
|
|
|
+ templateKey:
|
|
|
+ 'notification_dropbox_unlinked_due_to_lapsed_reconfirmation',
|
|
|
+ }
|
|
|
+ window.metaAttributesCache.set('ol-notifications', [
|
|
|
+ merge(cloneDeep(notification), reconfiguredNotification),
|
|
|
+ ])
|
|
|
+
|
|
|
+ renderWithinProjectListProvider(Common)
|
|
|
+ await fetchMock.flush(true)
|
|
|
+ fetchMock.delete(`/notifications/${reconfiguredNotification._id}`, 200)
|
|
|
+
|
|
|
+ screen.getByRole('alert')
|
|
|
+ screen.getByText(/your Dropbox account has been unlinked/i)
|
|
|
+ screen.getByText(
|
|
|
+ /confirm you are still at the institution and on their license, or upgrade your account in order to relink your dropbox account/i
|
|
|
+ )
|
|
|
+
|
|
|
+ const learnMore = screen.getByRole('link', { name: /learn more/i })
|
|
|
+ expect(learnMore.getAttribute('href')).to.equal(
|
|
|
+ '/learn/how-to/Institutional_Email_Reconfirmation'
|
|
|
+ )
|
|
|
+ const closeBtn = screen.getByRole('button', { name: /close/i })
|
|
|
+ fireEvent.click(closeBtn)
|
|
|
+
|
|
|
+ expect(fetchMock.called()).to.be.true
|
|
|
+ expect(screen.queryByRole('alert')).to.be.null
|
|
|
+ })
|
|
|
+
|
|
|
+ it('shows non specific notification', async function () {
|
|
|
+ const reconfiguredNotification: DeepPartial<Notification> = {
|
|
|
+ _id: 1,
|
|
|
+ html: 'unspecific message',
|
|
|
+ }
|
|
|
+ window.metaAttributesCache.set('ol-notifications', [
|
|
|
+ merge(cloneDeep(notification), reconfiguredNotification),
|
|
|
+ ])
|
|
|
+
|
|
|
+ renderWithinProjectListProvider(Common)
|
|
|
+ await fetchMock.flush(true)
|
|
|
+ fetchMock.delete(`/notifications/${reconfiguredNotification._id}`, 200)
|
|
|
+
|
|
|
+ screen.getByRole('alert')
|
|
|
+ screen.getByText(reconfiguredNotification.html!)
|
|
|
+
|
|
|
+ const closeBtn = screen.getByRole('button', { name: /close/i })
|
|
|
+ fireEvent.click(closeBtn)
|
|
|
+
|
|
|
+ expect(fetchMock.called()).to.be.true
|
|
|
+ expect(screen.queryByRole('alert')).to.be.null
|
|
|
+ })
|
|
|
+ })
|
|
|
+
|
|
|
+ describe('<Institution>', function () {
|
|
|
+ beforeEach(function () {
|
|
|
+ window.metaAttributesCache = window.metaAttributesCache || new Map()
|
|
|
+ window.metaAttributesCache.set('ol-ExposedSettings', exposedSettings)
|
|
|
+ fetchMock.reset()
|
|
|
+ })
|
|
|
+
|
|
|
+ afterEach(function () {
|
|
|
+ fetchMock.reset()
|
|
|
+ })
|
|
|
+
|
|
|
+ it('shows sso available', function () {
|
|
|
+ const institution: DeepPartial<InstitutionType> = {
|
|
|
+ templateKey: 'notification_institution_sso_available',
|
|
|
+ }
|
|
|
+ window.metaAttributesCache.set('ol-notificationsInstitution', [
|
|
|
+ { ...notificationsInstitution, ...institution },
|
|
|
+ ])
|
|
|
+ render(<Institution />)
|
|
|
+
|
|
|
+ screen.getByRole('alert')
|
|
|
+ screen.getByText(/you can now link/i)
|
|
|
+ screen.getByText(/doing this will allow you to log in/i)
|
|
|
+
|
|
|
+ const learnMore = screen.getByRole('link', { name: /learn more/i })
|
|
|
+ expect(learnMore.getAttribute('href')).to.equal(
|
|
|
+ '/learn/how-to/Institutional_Login'
|
|
|
+ )
|
|
|
+
|
|
|
+ const action = screen.getByRole('link', { name: /link account/i })
|
|
|
+ expect(action.getAttribute('href')).to.equal(
|
|
|
+ `${exposedSettings.samlInitPath}?university_id=${notificationsInstitution.institutionId}&auto=/project&email=${notificationsInstitution.email}`
|
|
|
+ )
|
|
|
+ })
|
|
|
+
|
|
|
+ it('shows sso linked', function () {
|
|
|
+ const institution: DeepPartial<InstitutionType> = {
|
|
|
+ _id: 1,
|
|
|
+ templateKey: 'notification_institution_sso_linked',
|
|
|
+ }
|
|
|
+ window.metaAttributesCache.set('ol-notificationsInstitution', [
|
|
|
+ { ...notificationsInstitution, ...institution },
|
|
|
+ ])
|
|
|
+ render(<Institution />)
|
|
|
+ fetchMock.delete(`/notifications/${institution._id}`, 200)
|
|
|
+
|
|
|
+ screen.getByRole('alert')
|
|
|
+ screen.getByText(/has been linked to your/i)
|
|
|
+
|
|
|
+ const closeBtn = screen.getByRole('button', { name: /close/i })
|
|
|
+ fireEvent.click(closeBtn)
|
|
|
+
|
|
|
+ expect(fetchMock.called()).to.be.true
|
|
|
+ expect(screen.queryByRole('alert')).to.be.null
|
|
|
+ })
|
|
|
+
|
|
|
+ it('shows sso non canonical', function () {
|
|
|
+ const institution: DeepPartial<InstitutionType> = {
|
|
|
+ _id: 1,
|
|
|
+ templateKey: 'notification_institution_sso_non_canonical',
|
|
|
+ }
|
|
|
+ window.metaAttributesCache.set('ol-notificationsInstitution', [
|
|
|
+ { ...notificationsInstitution, ...institution },
|
|
|
+ ])
|
|
|
+ render(<Institution />)
|
|
|
+ fetchMock.delete(`/notifications/${institution._id}`, 200)
|
|
|
+
|
|
|
+ screen.getByRole('alert')
|
|
|
+ screen.getByText(/you’ve tried to login with/i)
|
|
|
+ screen.getByText(
|
|
|
+ /in order to match your institutional metadata, your account is associated with/i
|
|
|
+ )
|
|
|
+
|
|
|
+ const closeBtn = screen.getByRole('button', { name: /close/i })
|
|
|
+ fireEvent.click(closeBtn)
|
|
|
+
|
|
|
+ expect(fetchMock.called()).to.be.true
|
|
|
+ expect(screen.queryByRole('alert')).to.be.null
|
|
|
+ })
|
|
|
+
|
|
|
+ it('shows sso already registered', function () {
|
|
|
+ const institution: DeepPartial<InstitutionType> = {
|
|
|
+ _id: 1,
|
|
|
+ templateKey: 'notification_institution_sso_already_registered',
|
|
|
+ }
|
|
|
+ window.metaAttributesCache.set('ol-notificationsInstitution', [
|
|
|
+ { ...notificationsInstitution, ...institution },
|
|
|
+ ])
|
|
|
+ render(<Institution />)
|
|
|
+ fetchMock.delete(`/notifications/${institution._id}`, 200)
|
|
|
+
|
|
|
+ screen.getByRole('alert')
|
|
|
+ screen.getByText(/which is already registered with/i)
|
|
|
+
|
|
|
+ const action = screen.getByRole('link', { name: /find out more/i })
|
|
|
+ expect(action.getAttribute('href')).to.equal(
|
|
|
+ '/learn/how-to/Institutional_Login'
|
|
|
+ )
|
|
|
+
|
|
|
+ const closeBtn = screen.getByRole('button', { name: /close/i })
|
|
|
+ fireEvent.click(closeBtn)
|
|
|
+
|
|
|
+ expect(fetchMock.called()).to.be.true
|
|
|
+ expect(screen.queryByRole('alert')).to.be.null
|
|
|
+ })
|
|
|
+
|
|
|
+ it('shows sso error', function () {
|
|
|
+ const institution: DeepPartial<InstitutionType> = {
|
|
|
+ templateKey: 'notification_institution_sso_error',
|
|
|
+ error: {
|
|
|
+ message: 'fake message',
|
|
|
+ tryAgain: true,
|
|
|
+ },
|
|
|
+ }
|
|
|
+ window.metaAttributesCache.set('ol-notificationsInstitution', [
|
|
|
+ { ...notificationsInstitution, ...institution },
|
|
|
+ ])
|
|
|
+ render(<Institution />)
|
|
|
+
|
|
|
+ screen.getByRole('alert')
|
|
|
+ screen.getByText(/something went wrong/i)
|
|
|
+ screen.getByText(institution.error!.message!)
|
|
|
+ screen.getByText(/please try again/i)
|
|
|
+
|
|
|
+ const closeBtn = screen.getByRole('button', { name: /close/i })
|
|
|
+ fireEvent.click(closeBtn)
|
|
|
+
|
|
|
+ expect(screen.queryByRole('alert')).to.be.null
|
|
|
+ })
|
|
|
+ })
|
|
|
+
|
|
|
+ describe('<ConfirmEmail/>', function () {
|
|
|
+ beforeEach(async function () {
|
|
|
+ window.metaAttributesCache.set('ol-ExposedSettings', {
|
|
|
+ emailConfirmationDisabled: false,
|
|
|
+ })
|
|
|
+ window.metaAttributesCache.set('ol-userEmails', [unconfirmedUserData])
|
|
|
+
|
|
|
+ renderWithinProjectListProvider(ConfirmEmail)
|
|
|
+ await fetchMock.flush(true)
|
|
|
+ })
|
|
|
+
|
|
|
+ afterEach(function () {
|
|
|
+ fetchMock.reset()
|
|
|
+ })
|
|
|
+
|
|
|
+ it('sends successfully', async function () {
|
|
|
+ fetchMock.post('/user/emails/resend_confirmation', 200)
|
|
|
+
|
|
|
+ const resendButton = screen.getByRole('button', { name: /resend/i })
|
|
|
+ fireEvent.click(resendButton)
|
|
|
+
|
|
|
+ expect(screen.queryByRole('button', { name: /resend/i })).to.be.null
|
|
|
+
|
|
|
+ await waitForElementToBeRemoved(() =>
|
|
|
+ screen.getByText(/resending confirmation email/i)
|
|
|
+ )
|
|
|
+
|
|
|
+ expect(fetchMock.called()).to.be.true
|
|
|
+ expect(screen.queryByRole('alert')).to.be.null
|
|
|
+ })
|
|
|
+
|
|
|
+ it('fails to send', async function () {
|
|
|
+ fetchMock.post('/user/emails/resend_confirmation', 500)
|
|
|
+
|
|
|
+ const resendButton = screen.getByRole('button', { name: /resend/i })
|
|
|
+ fireEvent.click(resendButton)
|
|
|
+
|
|
|
+ await waitForElementToBeRemoved(() =>
|
|
|
+ screen.getByText(/resending confirmation email/i)
|
|
|
+ )
|
|
|
+
|
|
|
+ expect(fetchMock.called()).to.be.true
|
|
|
+ screen.getByText(/something went wrong/i)
|
|
|
+ })
|
|
|
+ })
|
|
|
+
|
|
|
+ describe('<Affiliation/>', function () {
|
|
|
+ const locationStub = sinon.stub()
|
|
|
+ const originalLocation = window.location
|
|
|
+ beforeEach(function () {
|
|
|
+ window.metaAttributesCache = window.metaAttributesCache || new Map()
|
|
|
+ window.metaAttributesCache.set('ol-ExposedSettings', exposedSettings)
|
|
|
+ Object.defineProperty(window, 'location', {
|
|
|
+ value: { assign: locationStub },
|
|
|
+ })
|
|
|
+ fetchMock.reset()
|
|
|
+ })
|
|
|
+
|
|
|
+ afterEach(function () {
|
|
|
+ window.metaAttributesCache = new Map()
|
|
|
+ Object.defineProperty(window, 'location', {
|
|
|
+ value: originalLocation,
|
|
|
+ })
|
|
|
+ fetchMock.reset()
|
|
|
+ })
|
|
|
+
|
|
|
+ it('shows reconfirm message with SSO disabled', async function () {
|
|
|
+ window.metaAttributesCache.set('ol-allInReconfirmNotificationPeriods', [
|
|
|
+ { ...professionalUserData, samlProviderId: 'Saml Provider' },
|
|
|
+ ])
|
|
|
+
|
|
|
+ render(<ReconfirmationInfo />)
|
|
|
+ screen.getByRole('alert')
|
|
|
+ screen.getByText(
|
|
|
+ /take a moment to confirm your institutional email address/i
|
|
|
+ )
|
|
|
+
|
|
|
+ const removeLink = screen.getByRole('link', { name: /remove it/i })
|
|
|
+ expect(removeLink.getAttribute('href')).to.equal(
|
|
|
+ `/user/settings?remove=${professionalUserData.email}`
|
|
|
+ )
|
|
|
+ const learnMore = screen.getByRole('link', { name: /learn more/i })
|
|
|
+ expect(learnMore.getAttribute('href')).to.equal(
|
|
|
+ '/learn/how-to/Institutional_Email_Reconfirmation'
|
|
|
+ )
|
|
|
+
|
|
|
+ const sendReconfirmationMock = fetchMock.post(
|
|
|
+ '/user/emails/send-reconfirmation',
|
|
|
+ 200
|
|
|
+ )
|
|
|
+ fireEvent.click(
|
|
|
+ screen.getByRole('button', { name: /confirm affiliation/i })
|
|
|
+ )
|
|
|
+
|
|
|
+ await waitForElementToBeRemoved(() => screen.getByText(/sending/i))
|
|
|
+ screen.getByText(/check your email inbox to confirm/i)
|
|
|
+ expect(screen.queryByRole('button', { name: /confirm affiliation/i })).to
|
|
|
+ .be.null
|
|
|
+ expect(screen.queryByRole('link', { name: /remove it/i })).to.be.null
|
|
|
+ expect(screen.queryByRole('link', { name: /learn more/i })).to.be.null
|
|
|
+ expect(sendReconfirmationMock.called()).to.be.true
|
|
|
+ fireEvent.click(
|
|
|
+ screen.getByRole('button', { name: /resend confirmation email/i })
|
|
|
+ )
|
|
|
+ await waitForElementToBeRemoved(() => screen.getByText(/sending/i))
|
|
|
+ expect(sendReconfirmationMock.calls()).to.have.lengthOf(2)
|
|
|
+ })
|
|
|
+
|
|
|
+ it('shows reconfirm message with SSO enabled', async function () {
|
|
|
+ window.metaAttributesCache.set('ol-allInReconfirmNotificationPeriods', [
|
|
|
+ merge(cloneDeep(professionalUserData), {
|
|
|
+ affiliation: { institution: { ssoEnabled: true } },
|
|
|
+ }),
|
|
|
+ ])
|
|
|
+ render(<ReconfirmationInfo />)
|
|
|
+ fireEvent.click(
|
|
|
+ screen.getByRole('button', { name: /confirm affiliation/i })
|
|
|
+ )
|
|
|
+ sinon.assert.calledOnce(locationStub)
|
|
|
+ sinon.assert.calledWithMatch(
|
|
|
+ locationStub,
|
|
|
+ `${exposedSettings.samlInitPath}?university_id=${professionalUserData.affiliation.institution.id}&reconfirm=/project`
|
|
|
+ )
|
|
|
+ })
|
|
|
+
|
|
|
+ it('shows success reconfirmation message', function () {
|
|
|
+ window.metaAttributesCache.set('ol-userEmails', [
|
|
|
+ { ...professionalUserData, samlProviderId: 'Saml Provider' },
|
|
|
+ ])
|
|
|
+ window.metaAttributesCache.set('ol-reconfirmedViaSAML', 'Saml Provider')
|
|
|
+
|
|
|
+ const { rerender } = render(<ReconfirmationInfo />)
|
|
|
+ screen.getByRole('alert')
|
|
|
+ screen.getByText(professionalUserData.affiliation!.institution!.name!)
|
|
|
+ screen.getByText(/affiliation is confirmed/i)
|
|
|
+
|
|
|
+ window.metaAttributesCache.set('ol-reconfirmedViaSAML', '')
|
|
|
+ rerender(<ReconfirmationInfo />)
|
|
|
+ expect(screen.queryByRole('alert')).to.be.null
|
|
|
+ })
|
|
|
+ })
|
|
|
+})
|