password-section.test.tsx 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213
  1. import { expect } from 'chai'
  2. import { fireEvent, screen, render } from '@testing-library/react'
  3. import fetchMock from 'fetch-mock'
  4. import PasswordSection from '../../../../../frontend/js/features/settings/components/password-section'
  5. import getMeta from '@/utils/meta'
  6. describe('<PasswordSection />', function () {
  7. beforeEach(function () {
  8. Object.assign(getMeta('ol-ExposedSettings'), {
  9. isOverleaf: true,
  10. })
  11. window.metaAttributesCache.set(
  12. 'ol-isExternalAuthenticationSystemUsed',
  13. false
  14. )
  15. window.metaAttributesCache.set('ol-hasPassword', true)
  16. })
  17. afterEach(function () {
  18. fetchMock.removeRoutes().clearHistory()
  19. })
  20. it('shows password managed externally message', async function () {
  21. Object.assign(getMeta('ol-ExposedSettings'), {
  22. isOverleaf: false,
  23. })
  24. window.metaAttributesCache.set(
  25. 'ol-isExternalAuthenticationSystemUsed',
  26. true
  27. )
  28. render(<PasswordSection />)
  29. screen.getByText('Password settings are managed externally.')
  30. })
  31. it('shows no existing password message', async function () {
  32. window.metaAttributesCache.set('ol-hasPassword', false)
  33. render(<PasswordSection />)
  34. screen.getByText('Please use the password reset form to set your password')
  35. })
  36. it('submits all inputs', async function () {
  37. const updateMock = fetchMock.post('/user/password/update', 200)
  38. render(<PasswordSection />)
  39. submitValidForm()
  40. expect(updateMock.callHistory.called()).to.be.true
  41. expect(
  42. JSON.parse(updateMock.callHistory.calls().at(-1)?.options.body as string)
  43. ).to.deep.equal({
  44. currentPassword: 'foobar',
  45. newPassword1: 'barbaz',
  46. newPassword2: 'barbaz',
  47. })
  48. })
  49. it('disables button on invalid form', async function () {
  50. const updateMock = fetchMock.post('/user/password/update', 200)
  51. render(<PasswordSection />)
  52. fireEvent.click(
  53. screen.getByRole('button', {
  54. name: 'Change',
  55. })
  56. )
  57. expect(updateMock.callHistory.called()).to.be.false
  58. })
  59. it('validates inputs', async function () {
  60. render(<PasswordSection />)
  61. const button = screen.getByRole('button', {
  62. name: 'Change',
  63. }) as HTMLButtonElement
  64. expect(button.disabled).to.be.true
  65. fireEvent.change(screen.getByLabelText('Current password'), {
  66. target: { value: 'foobar' },
  67. })
  68. expect(button.disabled).to.be.true
  69. fireEvent.change(screen.getByLabelText('New password'), {
  70. target: { value: 'barbaz' },
  71. })
  72. expect(button.disabled).to.be.true
  73. fireEvent.change(screen.getByLabelText('Confirm new password'), {
  74. target: { value: 'bar' },
  75. })
  76. screen.getByText('Doesn’t match')
  77. expect(button.disabled).to.be.true
  78. fireEvent.change(screen.getByLabelText('Confirm new password'), {
  79. target: { value: 'barbaz' },
  80. })
  81. expect(button.disabled).to.be.false
  82. })
  83. it('sets browser validation attributes', async function () {
  84. window.metaAttributesCache.set('ol-passwordStrengthOptions', {
  85. length: {
  86. min: 3,
  87. },
  88. })
  89. render(<PasswordSection />)
  90. const currentPasswordInput = screen.getByLabelText(
  91. 'Current password'
  92. ) as HTMLInputElement
  93. const newPassword1Input = screen.getByLabelText(
  94. 'New password'
  95. ) as HTMLInputElement
  96. const newPassword2Input = screen.getByLabelText(
  97. 'Confirm new password'
  98. ) as HTMLInputElement
  99. expect(newPassword1Input.minLength).to.equal(3)
  100. // not required before changes
  101. expect(currentPasswordInput.required).to.be.false
  102. expect(newPassword1Input.required).to.be.false
  103. expect(newPassword2Input.required).to.be.false
  104. fireEvent.change(currentPasswordInput, {
  105. target: { value: 'foobar' },
  106. })
  107. fireEvent.change(newPassword1Input, {
  108. target: { value: 'barbaz' },
  109. })
  110. fireEvent.change(newPassword2Input, {
  111. target: { value: 'barbaz' },
  112. })
  113. expect(currentPasswordInput.required).to.be.true
  114. expect(newPassword1Input.required).to.be.true
  115. expect(newPassword2Input.required).to.be.true
  116. })
  117. it('shows inflight state and success message', async function () {
  118. let finishUpdateCall: (value: any) => void = () => {}
  119. fetchMock.post(
  120. '/user/password/update',
  121. new Promise(resolve => (finishUpdateCall = resolve))
  122. )
  123. render(<PasswordSection />)
  124. submitValidForm()
  125. await screen.findByRole('button', { name: 'Saving…' })
  126. finishUpdateCall({
  127. status: 200,
  128. body: {
  129. message: {
  130. type: 'success',
  131. email: 'tim.alby@overleaf.com',
  132. text: 'Password changed',
  133. },
  134. },
  135. })
  136. await screen.findByRole('button', {
  137. name: 'Change',
  138. })
  139. screen.getByText('Password changed')
  140. })
  141. it('shows server error', async function () {
  142. fetchMock.post('/user/password/update', 500)
  143. render(<PasswordSection />)
  144. submitValidForm()
  145. await screen.findByText('Something went wrong. Please try again.')
  146. })
  147. it('shows server error message', async function () {
  148. fetchMock.post('/user/password/update', {
  149. status: 400,
  150. body: {
  151. message: 'Your old password is wrong',
  152. },
  153. })
  154. render(<PasswordSection />)
  155. submitValidForm()
  156. await screen.findByText('Your old password is wrong')
  157. })
  158. it('shows message when user cannot use password log in', async function () {
  159. window.metaAttributesCache.set('ol-cannot-change-password', true)
  160. render(<PasswordSection />)
  161. await screen.findByRole('heading', { name: 'Change password' })
  162. screen.getByText(
  163. 'You can’t add or change your password because your group or organization uses',
  164. { exact: false }
  165. )
  166. screen.getByRole('link', { name: 'single sign-on (SSO)' })
  167. })
  168. })
  169. function submitValidForm() {
  170. fireEvent.change(screen.getByLabelText('Current password'), {
  171. target: { value: 'foobar' },
  172. })
  173. fireEvent.change(screen.getByLabelText('New password'), {
  174. target: { value: 'barbaz' },
  175. })
  176. fireEvent.change(screen.getByLabelText('Confirm new password'), {
  177. target: { value: 'barbaz' },
  178. })
  179. fireEvent.click(
  180. screen.getByRole('button', {
  181. name: 'Change',
  182. })
  183. )
  184. }