delete-entity.test.js 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273
  1. import { expect } from 'chai'
  2. import React from 'react'
  3. import sinon from 'sinon'
  4. import { screen, render, fireEvent, waitFor } from '@testing-library/react'
  5. import fetchMock from 'fetch-mock'
  6. import MockedSocket from 'socket.io-mock'
  7. import FileTreeRoot from '../../../../../frontend/js/features/file-tree/components/file-tree-root'
  8. describe('FileTree Delete Entity Flow', function() {
  9. const onSelect = sinon.stub()
  10. const onInit = sinon.stub()
  11. beforeEach(function() {
  12. window._ide = {
  13. socket: new MockedSocket()
  14. }
  15. })
  16. afterEach(function() {
  17. fetchMock.restore()
  18. onSelect.reset()
  19. onInit.reset()
  20. delete window._ide
  21. })
  22. describe('single entity', function() {
  23. beforeEach(function() {
  24. const rootFolder = [
  25. {
  26. _id: 'root-folder-id',
  27. docs: [{ _id: '456def', name: 'main.tex' }],
  28. folders: [],
  29. fileRefs: []
  30. }
  31. ]
  32. render(
  33. <FileTreeRoot
  34. rootFolder={rootFolder}
  35. projectId="123abc"
  36. hasWritePermissions
  37. hasFeature={() => true}
  38. refProviders={{}}
  39. reindexReferences={() => null}
  40. setRefProviderEnabled={() => null}
  41. setStartedFreeTrial={() => null}
  42. onSelect={onSelect}
  43. onInit={onInit}
  44. isConnected
  45. />
  46. )
  47. const treeitem = screen.getByRole('treeitem', { name: 'main.tex' })
  48. fireEvent.click(treeitem)
  49. const deleteButton = screen.getByRole('menuitem', { name: 'Delete' })
  50. fireEvent.click(deleteButton)
  51. })
  52. it('removes item', async function() {
  53. const fetchMatcher = /\/project\/\w+\/doc\/\w+/
  54. fetchMock.delete(fetchMatcher, 204)
  55. const modalDeleteButton = await getModalDeleteButton()
  56. fireEvent.click(modalDeleteButton)
  57. window._ide.socket.socketClient.emit('removeEntity', '456def')
  58. await waitFor(() => {
  59. expect(
  60. screen.queryByRole('treeitem', {
  61. name: 'main.tex',
  62. hidden: true // treeitem might be hidden behind the modal
  63. })
  64. ).to.not.exist
  65. expect(
  66. screen.queryByRole('treeitem', {
  67. name: 'main.tex'
  68. })
  69. ).to.not.exist
  70. // check that the confirmation modal is closed
  71. expect(screen.queryByText(/Are you sure/)).to.not.exist
  72. })
  73. const [lastFetchPath] = fetchMock.lastCall(fetchMatcher)
  74. expect(lastFetchPath).to.equal('/project/123abc/doc/456def')
  75. })
  76. it('continues delete on 404s', async function() {
  77. fetchMock.delete(/\/project\/\w+\/doc\/\w+/, 404)
  78. const modalDeleteButton = await getModalDeleteButton()
  79. fireEvent.click(modalDeleteButton)
  80. window._ide.socket.socketClient.emit('removeEntity', '456def')
  81. // check that the confirmation modal is open
  82. screen.getByText(/Are you sure/)
  83. await waitFor(() => {
  84. expect(
  85. screen.queryByRole('treeitem', {
  86. name: 'main.tex',
  87. hidden: true // treeitem might be hidden behind the modal
  88. })
  89. ).to.not.exist
  90. expect(
  91. screen.queryByRole('treeitem', {
  92. name: 'main.tex'
  93. })
  94. ).to.not.exist
  95. // check that the confirmation modal is closed
  96. // is not, the 404 probably triggered a bug
  97. expect(screen.queryByText(/Are you sure/)).to.not.exist
  98. })
  99. })
  100. it('aborts delete on error', async function() {
  101. const fetchMatcher = /\/project\/\w+\/doc\/\w+/
  102. fetchMock.delete(fetchMatcher, 500)
  103. const modalDeleteButton = await getModalDeleteButton()
  104. fireEvent.click(modalDeleteButton)
  105. // The modal should still be open, but the file should not be deleted
  106. await screen.findByRole('treeitem', { name: 'main.tex', hidden: true })
  107. })
  108. })
  109. describe('folders', function() {
  110. beforeEach(function() {
  111. const rootFolder = [
  112. {
  113. docs: [{ _id: '456def', name: 'main.tex' }],
  114. folders: [
  115. {
  116. _id: '123abc',
  117. name: 'folder',
  118. docs: [],
  119. folders: [],
  120. fileRefs: [{ _id: '789ghi', name: 'my.bib' }]
  121. }
  122. ],
  123. fileRefs: []
  124. }
  125. ]
  126. render(
  127. <FileTreeRoot
  128. rootFolder={rootFolder}
  129. projectId="123abc"
  130. hasWritePermissions
  131. hasFeature={() => true}
  132. refProviders={{}}
  133. reindexReferences={() => null}
  134. setRefProviderEnabled={() => null}
  135. setStartedFreeTrial={() => null}
  136. onSelect={onSelect}
  137. onInit={onInit}
  138. isConnected
  139. />
  140. )
  141. const expandButton = screen.queryByRole('button', { name: 'Expand' })
  142. if (expandButton) fireEvent.click(expandButton)
  143. const treeitemDoc = screen.getByRole('treeitem', { name: 'main.tex' })
  144. fireEvent.click(treeitemDoc)
  145. const treeitemFile = screen.getByRole('treeitem', { name: 'my.bib' })
  146. fireEvent.click(treeitemFile, { ctrlKey: true })
  147. window._ide.socket.socketClient.emit('removeEntity', '123abc')
  148. })
  149. it('removes the folder', function() {
  150. expect(screen.queryByRole('treeitem', { name: 'folder' })).to.not.exist
  151. })
  152. it('leaves the main file selected', function() {
  153. screen.getByRole('treeitem', { name: 'main.tex', selected: true })
  154. })
  155. it('unselect the child entity', async function() {
  156. // as a proxy to check that the child entity has been unselect we start
  157. // a delete and ensure the modal is displayed (the cancel button can be
  158. // selected) This is needed to make sure the test fail.
  159. const deleteButton = screen.getByRole('menuitem', { name: 'Delete' })
  160. fireEvent.click(deleteButton)
  161. await waitFor(() => screen.getByRole('button', { name: 'Cancel' }))
  162. })
  163. })
  164. describe('multiple entities', function() {
  165. beforeEach(function() {
  166. const rootFolder = [
  167. {
  168. _id: 'root-folder-id',
  169. docs: [{ _id: '456def', name: 'main.tex' }],
  170. folders: [],
  171. fileRefs: [{ _id: '789ghi', name: 'my.bib' }]
  172. }
  173. ]
  174. render(
  175. <FileTreeRoot
  176. rootFolder={rootFolder}
  177. projectId="123abc"
  178. hasWritePermissions
  179. hasFeature={() => true}
  180. refProviders={{}}
  181. reindexReferences={() => null}
  182. setRefProviderEnabled={() => null}
  183. setStartedFreeTrial={() => null}
  184. onSelect={onSelect}
  185. onInit={onInit}
  186. isConnected
  187. />
  188. )
  189. const treeitemDoc = screen.getByRole('treeitem', { name: 'main.tex' })
  190. fireEvent.click(treeitemDoc)
  191. const treeitemFile = screen.getByRole('treeitem', { name: 'my.bib' })
  192. fireEvent.click(treeitemFile, { ctrlKey: true })
  193. const deleteButton = screen.getAllByRole('menuitem', {
  194. name: 'Delete'
  195. })[0]
  196. fireEvent.click(deleteButton)
  197. })
  198. it('removes all items', async function() {
  199. const fetchMatcher = /\/project\/\w+\/(doc|file)\/\w+/
  200. fetchMock.delete(fetchMatcher, 204)
  201. const modalDeleteButton = await getModalDeleteButton()
  202. fireEvent.click(modalDeleteButton)
  203. window._ide.socket.socketClient.emit('removeEntity', '456def')
  204. window._ide.socket.socketClient.emit('removeEntity', '789ghi')
  205. await waitFor(() => {
  206. for (const name of ['main.tex', 'my.bib']) {
  207. expect(
  208. screen.queryByRole('treeitem', {
  209. name,
  210. hidden: true // treeitem might be hidden behind the modal
  211. })
  212. ).to.not.exist
  213. expect(
  214. screen.queryByRole('treeitem', {
  215. name
  216. })
  217. ).to.not.exist
  218. // check that the confirmation modal is closed
  219. expect(screen.queryByText(/Are you sure/)).to.not.exist
  220. }
  221. })
  222. const [firstFetchPath, secondFetchPath] = fetchMock
  223. .calls()
  224. .map(([url]) => url)
  225. expect(firstFetchPath).to.equal('/project/123abc/doc/456def')
  226. expect(secondFetchPath).to.equal('/project/123abc/file/789ghi')
  227. })
  228. })
  229. async function getModalDeleteButton() {
  230. return waitFor(() => screen.getByRole('button', { name: 'Delete' }))
  231. }
  232. })