modal-form.test.tsx 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  1. import { expect } from 'chai'
  2. import sinon from 'sinon'
  3. import { fireEvent, screen, render, waitFor } from '@testing-library/react'
  4. import fetchMock, { type FetchMock } from 'fetch-mock'
  5. import LeaveModalForm from '../../../../../../frontend/js/features/settings/components/leave/modal-form'
  6. import { location } from '@/shared/components/location'
  7. import getMeta from '@/utils/meta'
  8. describe('<LeaveModalForm />', function () {
  9. beforeEach(function () {
  10. window.metaAttributesCache.set('ol-usersEmail', 'foo@bar.com')
  11. Object.assign(getMeta('ol-ExposedSettings'), { isOverleaf: true })
  12. })
  13. afterEach(function () {
  14. fetchMock.removeRoutes().clearHistory()
  15. })
  16. it('validates form', async function () {
  17. const setIsFormValid = sinon.stub()
  18. render(
  19. <LeaveModalForm
  20. setInFlight={() => {}}
  21. isFormValid={false}
  22. setIsFormValid={setIsFormValid}
  23. />
  24. )
  25. const emailInput = screen.getByLabelText('Email')
  26. fireEvent.change(emailInput, { target: { value: 'FOO@bar.com' } })
  27. const passwordInput = screen.getByLabelText('Password')
  28. fireEvent.change(passwordInput, { target: { value: 'foobar' } })
  29. const checkbox = screen.getByLabelText(
  30. 'I understand this will delete all projects in my Overleaf account with email address foo@bar.com'
  31. )
  32. fireEvent.click(checkbox)
  33. const setIsFormValidCalls = setIsFormValid.getCalls()
  34. const lastSetIsFormValidCall = setIsFormValidCalls.pop()
  35. expect(lastSetIsFormValidCall!.args[0]).to.be.true
  36. for (const setIsFormValidCall of setIsFormValidCalls) {
  37. expect(setIsFormValidCall.args[0]).to.be.false
  38. }
  39. })
  40. describe('submits', async function () {
  41. let setInFlight: sinon.SinonStub
  42. let setIsFormValid: sinon.SinonStub
  43. let deleteMock: FetchMock
  44. beforeEach(function () {
  45. setInFlight = sinon.stub()
  46. setIsFormValid = sinon.stub()
  47. deleteMock = fetchMock.post('/user/delete', 200)
  48. this.locationWrapperSandbox = sinon.createSandbox()
  49. this.locationWrapperStub = this.locationWrapperSandbox.stub(location)
  50. Object.assign(getMeta('ol-ExposedSettings'), { isOverleaf: true })
  51. })
  52. afterEach(function () {
  53. fetchMock.removeRoutes().clearHistory()
  54. this.locationWrapperSandbox.restore()
  55. })
  56. it('with valid form', async function () {
  57. render(
  58. <LeaveModalForm
  59. setInFlight={setInFlight}
  60. isFormValid
  61. setIsFormValid={setIsFormValid}
  62. />
  63. )
  64. fireEvent.submit(screen.getByLabelText('Email'))
  65. sinon.assert.calledOnce(setInFlight)
  66. sinon.assert.calledWithMatch(setInFlight, true)
  67. expect(deleteMock.callHistory.called()).to.be.true
  68. const assignStub = this.locationWrapperStub.assign
  69. await waitFor(() => {
  70. sinon.assert.calledTwice(setInFlight)
  71. sinon.assert.calledWithMatch(setInFlight, false)
  72. sinon.assert.calledOnce(assignStub)
  73. sinon.assert.calledWith(assignStub, '/')
  74. })
  75. })
  76. it('with invalid form', async function () {
  77. render(
  78. <LeaveModalForm
  79. setInFlight={setInFlight}
  80. isFormValid={false}
  81. setIsFormValid={setIsFormValid}
  82. />
  83. )
  84. fireEvent.submit(screen.getByLabelText('Email'))
  85. expect(deleteMock.callHistory.called()).to.be.false
  86. sinon.assert.notCalled(setInFlight)
  87. })
  88. })
  89. it('handles credentials error without Saas tip', async function () {
  90. Object.assign(getMeta('ol-ExposedSettings'), { isOverleaf: false })
  91. fetchMock.post('/user/delete', 403)
  92. render(
  93. <LeaveModalForm
  94. setInFlight={() => {}}
  95. isFormValid
  96. setIsFormValid={() => {}}
  97. />
  98. )
  99. fireEvent.submit(screen.getByLabelText('Email'))
  100. await waitFor(() => {
  101. screen.getByText(/Your email or password is incorrect. Please try again/)
  102. })
  103. expect(screen.queryByText(/If you cannot remember your password/)).to.not
  104. .exist
  105. })
  106. it('handles credentials error with Saas tip', async function () {
  107. fetchMock.post('/user/delete', 403)
  108. render(
  109. <LeaveModalForm
  110. setInFlight={() => {}}
  111. isFormValid
  112. setIsFormValid={() => {}}
  113. />
  114. )
  115. fireEvent.submit(screen.getByLabelText('Email'))
  116. await waitFor(() => {
  117. screen.getByText(/Your email or password is incorrect. Please try again/)
  118. })
  119. screen.getByText(/If you cannot remember your password/)
  120. const link = screen.getByRole('link', { name: 'reset your password' })
  121. expect(link.getAttribute('href')).to.equal('/user/password/reset')
  122. })
  123. it('handles subscription error', async function () {
  124. fetchMock.post('/user/delete', {
  125. status: 422,
  126. body: {
  127. error: 'SubscriptionAdminDeletionError',
  128. },
  129. })
  130. render(
  131. <LeaveModalForm
  132. setInFlight={() => {}}
  133. isFormValid
  134. setIsFormValid={() => {}}
  135. />
  136. )
  137. fireEvent.submit(screen.getByLabelText('Email'))
  138. await waitFor(() => {
  139. screen.getByText(
  140. 'You cannot delete your account while on a subscription. Please cancel your subscription and try again. If you keep seeing this message please contact us.'
  141. )
  142. })
  143. })
  144. it('handles generic error', async function () {
  145. fetchMock.post('/user/delete', 500)
  146. render(
  147. <LeaveModalForm
  148. setInFlight={() => {}}
  149. isFormValid
  150. setIsFormValid={() => {}}
  151. />
  152. )
  153. fireEvent.submit(screen.getByLabelText('Email'))
  154. await waitFor(() => {
  155. screen.getByText(
  156. 'Sorry, something went wrong deleting your account. Please try again in a minute.'
  157. )
  158. })
  159. })
  160. })