clone-project-modal.test.tsx 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202
  1. import { fireEvent, screen, waitFor } from '@testing-library/react'
  2. import { expect } from 'chai'
  3. import sinon from 'sinon'
  4. import fetchMock from 'fetch-mock'
  5. import EditorCloneProjectModalWrapper from '../../../../../frontend/js/features/clone-project-modal/components/editor-clone-project-modal-wrapper'
  6. import { renderWithEditorContext } from '../../../helpers/render-with-context'
  7. describe('<EditorCloneProjectModalWrapper />', function () {
  8. beforeEach(function () {
  9. fetchMock.removeRoutes().clearHistory()
  10. window.metaAttributesCache.set('ol-preventCompileOnLoad', true)
  11. })
  12. after(function () {
  13. fetchMock.removeRoutes().clearHistory()
  14. })
  15. const contextProps = {
  16. projectId: 'project-1',
  17. projectName: 'Test Project',
  18. }
  19. it('renders the translated modal title', async function () {
  20. const handleHide = sinon.stub()
  21. const openProject = sinon.stub()
  22. renderWithEditorContext(
  23. <EditorCloneProjectModalWrapper
  24. handleHide={handleHide}
  25. openProject={openProject}
  26. show
  27. />,
  28. contextProps
  29. )
  30. await screen.findByText('Copy project')
  31. })
  32. it('posts the generated project name', async function () {
  33. fetchMock.post(
  34. 'express:/project/:projectId/clone',
  35. {
  36. status: 200,
  37. body: { project_id: 'cloned-project' },
  38. },
  39. { delay: 10 }
  40. )
  41. const handleHide = sinon.stub()
  42. const openProject = sinon.stub()
  43. renderWithEditorContext(
  44. <EditorCloneProjectModalWrapper
  45. handleHide={handleHide}
  46. openProject={openProject}
  47. show
  48. />,
  49. contextProps
  50. )
  51. const cancelButton: HTMLButtonElement = await screen.findByRole('button', {
  52. name: 'Cancel',
  53. })
  54. expect(cancelButton.disabled).to.be.false
  55. const submitButton: HTMLButtonElement = await screen.findByRole('button', {
  56. name: 'Copy',
  57. })
  58. expect(submitButton.disabled).to.be.false
  59. const input = await screen.getByLabelText(/New name/i)
  60. fireEvent.change(input, {
  61. target: { value: '' },
  62. })
  63. expect(submitButton.disabled).to.be.true
  64. fireEvent.change(input, {
  65. target: { value: 'A Cloned Project' },
  66. })
  67. expect(submitButton.disabled).to.be.false
  68. fireEvent.click(submitButton)
  69. expect(submitButton.disabled).to.be.true
  70. await fetchMock.callHistory.flush(true)
  71. expect(fetchMock.callHistory.done()).to.be.true
  72. const callLog = fetchMock.callHistory
  73. .calls('express:/project/:projectId/clone')
  74. .at(-1)
  75. expect(callLog).to.exist
  76. const { url, options } = callLog!
  77. expect(url).to.equal(
  78. 'https://www.test-overleaf.com/project/project-1/clone'
  79. )
  80. expect(options.body).to.exist
  81. expect(JSON.parse(options.body as string)).to.deep.equal({
  82. projectName: 'A Cloned Project',
  83. tags: [],
  84. })
  85. await waitFor(() => {
  86. expect(openProject).to.be.calledOnce
  87. })
  88. const errorMessage = screen.queryByText('Sorry, something went wrong')
  89. expect(errorMessage).to.be.null
  90. await waitFor(() => {
  91. expect(submitButton.disabled).to.be.false
  92. expect(cancelButton.disabled).to.be.false
  93. })
  94. })
  95. it('handles a generic error response', async function () {
  96. const matcher = 'express:/project/:projectId/clone'
  97. fetchMock.postOnce(matcher, {
  98. status: 500,
  99. body: 'There was an error!',
  100. })
  101. const handleHide = sinon.stub()
  102. const openProject = sinon.stub()
  103. renderWithEditorContext(
  104. <EditorCloneProjectModalWrapper
  105. handleHide={handleHide}
  106. openProject={openProject}
  107. show
  108. />,
  109. contextProps
  110. )
  111. const button: HTMLButtonElement = await screen.findByRole('button', {
  112. name: 'Copy',
  113. })
  114. expect(button.disabled).to.be.false
  115. const cancelButton: HTMLButtonElement = await screen.findByRole('button', {
  116. name: 'Cancel',
  117. })
  118. expect(cancelButton.disabled).to.be.false
  119. fireEvent.click(button)
  120. expect(fetchMock.callHistory.done(matcher)).to.be.true
  121. expect(openProject).not.to.be.called
  122. await screen.findByText('Sorry, something went wrong')
  123. expect(button.disabled).to.be.false
  124. expect(cancelButton.disabled).to.be.false
  125. })
  126. it('handles a specific error response', async function () {
  127. const matcher = 'express:/project/:projectId/clone'
  128. fetchMock.postOnce(matcher, {
  129. status: 400,
  130. body: 'There was an error!',
  131. })
  132. const handleHide = sinon.stub()
  133. const openProject = sinon.stub()
  134. renderWithEditorContext(
  135. <EditorCloneProjectModalWrapper
  136. handleHide={handleHide}
  137. openProject={openProject}
  138. show
  139. />,
  140. contextProps
  141. )
  142. const button: HTMLButtonElement = await screen.findByRole('button', {
  143. name: 'Copy',
  144. })
  145. expect(button.disabled).to.be.false
  146. const cancelButton: HTMLButtonElement = await screen.findByRole('button', {
  147. name: 'Cancel',
  148. })
  149. expect(cancelButton.disabled).to.be.false
  150. fireEvent.click(button)
  151. await fetchMock.callHistory.flush(true)
  152. expect(fetchMock.callHistory.done(matcher)).to.be.true
  153. expect(openProject).not.to.be.called
  154. await screen.findByText('There was an error!')
  155. expect(button.disabled).to.be.false
  156. expect(cancelButton.disabled).to.be.false
  157. })
  158. })