emails-row.test.tsx 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  1. import { render, screen, fireEvent } from '@testing-library/react'
  2. import { expect } from 'chai'
  3. import fetchMock from 'fetch-mock'
  4. import { cloneDeep } from 'lodash'
  5. import EmailsRow from '../../../../../../frontend/js/features/settings/components/emails/row'
  6. import {
  7. professionalUserData,
  8. unconfirmedUserData,
  9. } from '../../fixtures/test-user-email-data'
  10. import { UserEmailData } from '../../../../../../types/user-email'
  11. import { UserEmailsProvider } from '../../../../../../frontend/js/features/settings/context/user-email-context'
  12. import { Affiliation } from '../../../../../../types/affiliation'
  13. import getMeta from '@/utils/meta'
  14. import { SplitTestProvider } from '@/shared/context/split-test-context'
  15. function renderEmailsRow(data: UserEmailData) {
  16. return render(
  17. <SplitTestProvider>
  18. <UserEmailsProvider>
  19. <EmailsRow userEmailData={data} />
  20. </UserEmailsProvider>
  21. </SplitTestProvider>
  22. )
  23. }
  24. function getByTextContent(text: string) {
  25. return screen.getAllByText(
  26. (content, node) =>
  27. content === text || node?.children[0]?.textContent === text
  28. )
  29. }
  30. describe('<EmailsRow/>', function () {
  31. beforeEach(function () {
  32. Object.assign(getMeta('ol-ExposedSettings'), {
  33. samlInitPath: '/saml',
  34. hasSamlBeta: true,
  35. })
  36. window.metaAttributesCache.set('ol-splitTestVariants', {})
  37. fetchMock.get('/user/emails?ensureAffiliation=true', [])
  38. })
  39. afterEach(function () {
  40. fetchMock.removeRoutes().clearHistory()
  41. })
  42. describe('with unaffiliated email data', function () {
  43. it('renders email info', function () {
  44. renderEmailsRow(unconfirmedUserData)
  45. screen.getByText('baz@overleaf.com')
  46. })
  47. it('renders actions', function () {
  48. renderEmailsRow(unconfirmedUserData)
  49. screen.getByRole('button', { name: 'Make primary' })
  50. })
  51. })
  52. describe('with affiliated email data', function () {
  53. it('renders email info', function () {
  54. renderEmailsRow(professionalUserData)
  55. screen.getByText('foo@overleaf.com')
  56. screen.getByText('Primary')
  57. })
  58. it('renders actions', function () {
  59. renderEmailsRow(professionalUserData)
  60. screen.getByRole('button', { name: 'Remove' })
  61. })
  62. it('renders institution info', function () {
  63. renderEmailsRow(professionalUserData)
  64. screen.getByText('Overleaf')
  65. screen.getByText('Reader, Art History')
  66. })
  67. })
  68. describe('with email data affiliated to an institution with SSO available', function () {
  69. let affiliatedEmail: UserEmailData & { affiliation: Affiliation }
  70. beforeEach(function () {
  71. window.metaAttributesCache.get('ol-ExposedSettings').hasSamlFeature = true
  72. // make sure the institution has SSO available
  73. affiliatedEmail = cloneDeep(professionalUserData)
  74. affiliatedEmail.affiliation.institution.confirmed = true
  75. affiliatedEmail.affiliation.institution.isUniversity = true
  76. affiliatedEmail.affiliation.institution.ssoEnabled = true
  77. })
  78. describe('when the email is not yet linked to the institution', function () {
  79. beforeEach(async function () {
  80. fetchMock.removeRoutes().clearHistory()
  81. fetchMock.get(/\/user\/emails/, [affiliatedEmail, unconfirmedUserData])
  82. await fetchMock.callHistory.flush(true)
  83. })
  84. it('prompts the user to link to their institutional account', function () {
  85. renderEmailsRow(affiliatedEmail)
  86. getByTextContent(
  87. 'You can now link your Overleaf account to your Overleaf institutional account.'
  88. )
  89. screen.getByRole('button', { name: 'Link accounts' })
  90. })
  91. })
  92. describe('when the email is already linked to the institution', function () {
  93. beforeEach(async function () {
  94. affiliatedEmail.samlProviderId = '1'
  95. fetchMock.removeRoutes().clearHistory()
  96. fetchMock.get(/\/user\/emails/, [affiliatedEmail, unconfirmedUserData])
  97. await fetchMock.callHistory.flush(true)
  98. })
  99. it('prompts the user to login using their institutional account', function () {
  100. renderEmailsRow(affiliatedEmail)
  101. getByTextContent(
  102. 'You can log in to Overleaf through your Overleaf institutional login.'
  103. )
  104. expect(screen.queryByRole('button', { name: 'Link accounts' })).to.be
  105. .null
  106. })
  107. it('shows unlink button and opens unlink modal', function () {
  108. renderEmailsRow(affiliatedEmail)
  109. fireEvent.click(screen.getByRole('button', { name: 'Unlink SSO' }))
  110. screen.getByRole('dialog')
  111. screen.getByText('Unlink institutional login')
  112. })
  113. })
  114. describe('and domain capture is also on for group and Commons SSO also enabled', function () {
  115. // scenario of a Commons account migrating to a group account
  116. let affiliatedEmailWithDomainCaptureAndCommons: UserEmailData & {
  117. affiliation: Affiliation
  118. }
  119. beforeEach(async function () {
  120. fetchMock.removeRoutes().clearHistory()
  121. affiliatedEmailWithDomainCaptureAndCommons = cloneDeep(affiliatedEmail)
  122. affiliatedEmailWithDomainCaptureAndCommons.affiliation.group = {
  123. _id: 'grou123',
  124. domainCaptureEnabled: true,
  125. managedUsersEnabled: true,
  126. }
  127. await fetchMock.callHistory.flush(true)
  128. })
  129. it('does not prompt the user to link to their institutional account', function () {
  130. renderEmailsRow(affiliatedEmailWithDomainCaptureAndCommons)
  131. expect(() =>
  132. getByTextContent(
  133. 'You can now link your Overleaf account to your Overleaf institutional account.'
  134. )
  135. ).to.throw('Unable to find an element with the text')
  136. expect(screen.queryByRole('button', { name: 'Link accounts' })).to.be
  137. .null
  138. })
  139. it('still shows users can log in via Commons SSO if already linked', function () {
  140. affiliatedEmailWithDomainCaptureAndCommons.samlProviderId = '1'
  141. renderEmailsRow(affiliatedEmailWithDomainCaptureAndCommons)
  142. getByTextContent(
  143. 'You can log in to Overleaf through your Overleaf institutional login.'
  144. )
  145. expect(screen.queryByRole('button', { name: 'Link accounts' })).to.be
  146. .null
  147. })
  148. it('uses `domainCapturedByGroup` when the feature flag is enabled', function () {
  149. affiliatedEmailWithDomainCaptureAndCommons.affiliation.group = {
  150. _id: 'grou123',
  151. domainCaptureEnabled: true,
  152. managedUsersEnabled: true,
  153. }
  154. affiliatedEmailWithDomainCaptureAndCommons.affiliation.domainCapturedByGroup = true
  155. window.metaAttributesCache.set('ol-splitTestVariants', {
  156. 'domain-captured-by-group': 'enabled',
  157. })
  158. renderEmailsRow(affiliatedEmailWithDomainCaptureAndCommons)
  159. expect(screen.queryByRole('button', { name: 'Link accounts' })).to.be
  160. .null
  161. })
  162. it('ignores group domain capture when the feature flag is enabled and the domain is not captured', function () {
  163. affiliatedEmailWithDomainCaptureAndCommons.affiliation.domainCapturedByGroup = false
  164. window.metaAttributesCache.set('ol-splitTestVariants', {
  165. 'domain-captured-by-group': 'enabled',
  166. })
  167. renderEmailsRow(affiliatedEmailWithDomainCaptureAndCommons)
  168. getByTextContent(
  169. 'You can now link your Overleaf account to your Overleaf institutional account.'
  170. )
  171. screen.getByRole('button', { name: 'Link accounts' })
  172. })
  173. })
  174. })
  175. })