user-email-context.test.tsx 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279
  1. import { expect } from 'chai'
  2. import { cloneDeep } from 'lodash'
  3. import { renderHook, waitFor } from '@testing-library/react'
  4. import {
  5. EmailContextType,
  6. UserEmailsProvider,
  7. useUserEmailsContext,
  8. } from '../../../../../frontend/js/features/settings/context/user-email-context'
  9. import fetchMock from 'fetch-mock'
  10. import {
  11. confirmedUserData,
  12. professionalUserData,
  13. unconfirmedUserData,
  14. fakeUsersData,
  15. unconfirmedCommonsUserData,
  16. untrustedUserData,
  17. } from '../fixtures/test-user-email-data'
  18. const renderUserEmailsContext = () =>
  19. renderHook(() => useUserEmailsContext(), {
  20. wrapper: ({ children }) => (
  21. <UserEmailsProvider>{children}</UserEmailsProvider>
  22. ),
  23. })
  24. describe('UserEmailContext', function () {
  25. beforeEach(function () {
  26. fetchMock.removeRoutes().clearHistory()
  27. })
  28. describe('context bootstrap', function () {
  29. it('should start with an "in progress" initialisation state', function () {
  30. const { result } = renderUserEmailsContext()
  31. expect(result.current.isInitializing).to.equal(true)
  32. expect(result.current.isInitializingSuccess).to.equal(false)
  33. expect(result.current.isInitializingError).to.equal(false)
  34. })
  35. it('should start with an empty state', function () {
  36. const { result } = renderUserEmailsContext()
  37. expect(result.current.state.data.byId).to.deep.equal({})
  38. expect(result.current.state.data.emailAffiliationBeingEdited).to.be.null
  39. expect(result.current.state.data.linkedInstitutionIds).to.have.length(0)
  40. })
  41. it('should load all user emails and update the initialisation state to "success"', async function () {
  42. fetchMock.get(/\/user\/emails/, fakeUsersData)
  43. const { result } = renderUserEmailsContext()
  44. await fetchMock.callHistory.flush(true)
  45. expect(fetchMock.callHistory.calls()).to.have.lengthOf(1)
  46. await waitFor(() =>
  47. expect(result.current.state.data.byId).to.deep.equal({
  48. 'bar@overleaf.com': { ...untrustedUserData, ...confirmedUserData },
  49. 'baz@overleaf.com': unconfirmedUserData,
  50. 'foo@overleaf.com': professionalUserData,
  51. 'qux@overleaf.com': unconfirmedCommonsUserData,
  52. })
  53. )
  54. expect(result.current.state.data.linkedInstitutionIds).to.have.lengthOf(0)
  55. expect(result.current.isInitializing).to.equal(false)
  56. expect(result.current.isInitializingSuccess).to.equal(true)
  57. })
  58. it('when loading user email fails, it should update the initialisation state to "failed"', async function () {
  59. fetchMock.get(/\/user\/emails/, 500)
  60. const { result } = renderUserEmailsContext()
  61. await fetchMock.callHistory.flush()
  62. await waitFor(() => expect(result.current.isInitializing).to.equal(false))
  63. expect(result.current.isInitializingError).to.equal(true)
  64. })
  65. describe('state.isLoading', function () {
  66. it('should be `true` on bootstrap', function () {
  67. const { result } = renderUserEmailsContext()
  68. expect(result.current.state.isLoading).to.equal(true)
  69. })
  70. it('should be updated with `setLoading`', async function () {
  71. const { result } = renderUserEmailsContext()
  72. result.current.setLoading(true)
  73. await waitFor(() =>
  74. expect(result.current.state.isLoading).to.equal(true)
  75. )
  76. result.current.setLoading(false)
  77. await waitFor(() =>
  78. expect(result.current.state.isLoading).to.equal(false)
  79. )
  80. })
  81. })
  82. })
  83. describe('context initialised', function () {
  84. let result: { current: EmailContextType }
  85. beforeEach(async function () {
  86. fetchMock.get(/\/user\/emails/, fakeUsersData)
  87. const value = renderUserEmailsContext()
  88. result = value.result
  89. await fetchMock.callHistory.flush(true)
  90. })
  91. describe('getEmails()', function () {
  92. beforeEach(async function () {
  93. fetchMock.removeRoutes().clearHistory()
  94. })
  95. it('should set `isLoading === true`', async function () {
  96. fetchMock.get(
  97. /\/user\/emails/,
  98. [
  99. {
  100. email: 'new@email.com',
  101. default: true,
  102. },
  103. ],
  104. { delay: 100 }
  105. )
  106. result.current.getEmails()
  107. await waitFor(() => expect(result.current.state.isLoading).to.be.true)
  108. })
  109. it('requests a new set of emails', async function () {
  110. const emailData = {
  111. email: 'new@email.com',
  112. default: true,
  113. }
  114. fetchMock.get(/\/user\/emails/, [emailData])
  115. result.current.getEmails()
  116. await fetchMock.callHistory.flush(true)
  117. await waitFor(() =>
  118. expect(result.current.state.data.byId).to.deep.equal({
  119. 'new@email.com': emailData,
  120. })
  121. )
  122. })
  123. it('should populate `linkedInstitutionIds`', async function () {
  124. fetchMock.get(/\/user\/emails/, [
  125. confirmedUserData,
  126. { ...unconfirmedUserData, samlProviderId: 'saml_provider_1' },
  127. { ...professionalUserData, samlProviderId: 'saml_provider_2' },
  128. ])
  129. const { result } = renderUserEmailsContext()
  130. await fetchMock.callHistory.flush(true)
  131. await waitFor(() =>
  132. expect(result.current.state.data.linkedInstitutionIds).to.deep.equal([
  133. 'saml_provider_1',
  134. 'saml_provider_2',
  135. ])
  136. )
  137. })
  138. })
  139. describe('makePrimary()', function () {
  140. it('sets an email as `default`', async function () {
  141. expect(result.current.state.data.byId['bar@overleaf.com'].default).to.be
  142. .false
  143. result.current.makePrimary('bar@overleaf.com')
  144. await waitFor(
  145. () =>
  146. expect(result.current.state.data.byId['bar@overleaf.com'].default)
  147. .to.be.true
  148. )
  149. })
  150. it('sets `default=false` for the current primary email ', async function () {
  151. expect(result.current.state.data.byId['foo@overleaf.com'].default).to.be
  152. .true
  153. result.current.makePrimary('bar@overleaf.com')
  154. await waitFor(
  155. () =>
  156. expect(result.current.state.data.byId['foo@overleaf.com'].default)
  157. .to.be.false
  158. )
  159. })
  160. it('produces no effect when passing a non-existing email', async function () {
  161. const emails = cloneDeep(result.current.state.data.byId)
  162. result.current.makePrimary('non-existing@email.com')
  163. await waitFor(() =>
  164. expect(result.current.state.data.byId).to.deep.equal(emails)
  165. )
  166. })
  167. })
  168. describe('deleteEmail()', function () {
  169. it('removes data from the deleted email', async function () {
  170. result.current.deleteEmail('bar@overleaf.com')
  171. await waitFor(
  172. () =>
  173. expect(result.current.state.data.byId['bar@overleaf.com']).to.be
  174. .undefined
  175. )
  176. })
  177. it('produces no effect when passing a non-existing email', function () {
  178. const emails = cloneDeep(result.current.state.data.byId)
  179. result.current.deleteEmail('non-existing@email.com')
  180. expect(result.current.state.data.byId).to.deep.equal(emails)
  181. })
  182. })
  183. describe('setEmailAffiliationBeingEdited()', function () {
  184. it('sets an email as currently being edited', async function () {
  185. result.current.setEmailAffiliationBeingEdited('bar@overleaf.com')
  186. await waitFor(() =>
  187. expect(
  188. result.current.state.data.emailAffiliationBeingEdited
  189. ).to.equal('bar@overleaf.com')
  190. )
  191. result.current.setEmailAffiliationBeingEdited(null)
  192. await waitFor(
  193. () =>
  194. expect(result.current.state.data.emailAffiliationBeingEdited).to.be
  195. .null
  196. )
  197. })
  198. it('produces no effect when passing a non-existing email', function () {
  199. expect(result.current.state.data.emailAffiliationBeingEdited).to.be.null
  200. result.current.setEmailAffiliationBeingEdited('non-existing@email.com')
  201. expect(result.current.state.data.emailAffiliationBeingEdited).to.be.null
  202. })
  203. })
  204. describe('updateAffiliation()', function () {
  205. it('updates affiliation data for an email', async function () {
  206. result.current.updateAffiliation(
  207. 'foo@overleaf.com',
  208. 'new role',
  209. 'new department'
  210. )
  211. await waitFor(() =>
  212. expect(
  213. result.current.state.data.byId['foo@overleaf.com'].affiliation!.role
  214. ).to.equal('new role')
  215. )
  216. expect(
  217. result.current.state.data.byId['foo@overleaf.com'].affiliation!
  218. .department
  219. ).to.equal('new department')
  220. })
  221. it('clears an email from currently being edited', function () {
  222. result.current.setEmailAffiliationBeingEdited('foo@overleaf.com')
  223. result.current.updateAffiliation(
  224. 'foo@overleaf.com',
  225. 'new role',
  226. 'new department'
  227. )
  228. expect(result.current.state.data.emailAffiliationBeingEdited).to.be.null
  229. })
  230. it('produces no effect when passing an email with no affiliation', function () {
  231. const emails = cloneDeep(result.current.state.data.byId)
  232. result.current.updateAffiliation(
  233. 'bar@overleaf.com',
  234. 'new role',
  235. 'new department'
  236. )
  237. expect(result.current.state.data.byId).to.deep.equal(emails)
  238. })
  239. it('produces no effect when passing a non-existing email', function () {
  240. const emails = cloneDeep(result.current.state.data.byId)
  241. result.current.updateAffiliation(
  242. 'non-existing@email.com',
  243. 'new role',
  244. 'new department'
  245. )
  246. expect(result.current.state.data.byId).to.deep.equal(emails)
  247. })
  248. })
  249. })
  250. })