delete-entity.spec.tsx 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292
  1. import FileTreeRoot from '../../../../../frontend/js/features/file-tree/components/file-tree-root'
  2. import { EditorProviders } from '../../../helpers/editor-providers'
  3. import { SocketIOMock } from '@/ide/connection/SocketIoShim'
  4. import type { Socket } from '@/features/ide-react/connection/types/socket'
  5. describe('FileTree Delete Entity Flow', function () {
  6. beforeEach(function () {
  7. cy.window().then(win => {
  8. win.metaAttributesCache.set('ol-user', { id: 'user1' })
  9. })
  10. })
  11. describe('single entity', function () {
  12. let socket: SocketIOMock & Socket
  13. beforeEach(function () {
  14. socket = new SocketIOMock() as any
  15. const rootFolder = [
  16. {
  17. _id: 'root-folder-id',
  18. name: 'rootFolder',
  19. docs: [
  20. { _id: '123abc', name: 'foo.tex' },
  21. { _id: '456def', name: 'main.tex' },
  22. ],
  23. folders: [],
  24. fileRefs: [],
  25. },
  26. ]
  27. cy.mount(
  28. <div style={{ width: 400 }}>
  29. <EditorProviders
  30. rootFolder={rootFolder as any}
  31. projectId="123abc"
  32. socket={socket}
  33. >
  34. <FileTreeRoot
  35. refProviders={{}}
  36. setRefProviderEnabled={cy.stub()}
  37. setStartedFreeTrial={cy.stub()}
  38. onSelect={cy.stub()}
  39. onInit={cy.stub()}
  40. isConnected
  41. />
  42. </EditorProviders>
  43. </div>
  44. )
  45. cy.findByRole('treeitem', { name: 'main.tex' }).click()
  46. cy.findByRole('button', { name: 'Open main.tex action menu' }).click()
  47. cy.findByRole('menuitem', { name: 'Delete' }).click()
  48. })
  49. it('removes item', function () {
  50. cy.intercept('delete', '/project/*/doc/*', { statusCode: 204 }).as(
  51. 'deleteDoc'
  52. )
  53. cy.findByRole('dialog').within(() => {
  54. // check that the confirmation modal is open
  55. cy.findByText(
  56. 'Are you sure you want to permanently delete the following files?'
  57. )
  58. cy.findByRole('button', { name: 'Delete' }).click()
  59. })
  60. cy.wait('@deleteDoc')
  61. cy.then(() => {
  62. socket.emitToClient('removeEntity', '456def')
  63. })
  64. cy.findByRole('treeitem', {
  65. name: 'main.tex',
  66. hidden: true, // treeitem might be hidden behind the modal
  67. }).should('not.exist')
  68. cy.findByRole('treeitem', {
  69. name: 'main.tex',
  70. }).should('not.exist')
  71. // check that the confirmation modal is closed
  72. cy.findByText(
  73. 'Are you sure you want to permanently delete the following files?'
  74. ).should('not.exist')
  75. cy.get('@deleteDoc.all').should('have.length', 1)
  76. })
  77. it('continues delete on 404s', function () {
  78. cy.intercept('delete', '/project/*/doc/*', { statusCode: 404 }).as(
  79. 'deleteDoc'
  80. )
  81. cy.findByRole('dialog').within(() => {
  82. // check that the confirmation modal is open
  83. cy.findByText(
  84. 'Are you sure you want to permanently delete the following files?'
  85. )
  86. cy.findByRole('button', { name: 'Delete' }).click()
  87. })
  88. cy.then(() => {
  89. socket.emitToClient('removeEntity', '456def')
  90. })
  91. cy.findByRole('treeitem', {
  92. name: 'main.tex',
  93. hidden: true, // treeitem might be hidden behind the modal
  94. }).should('not.exist')
  95. cy.findByRole('treeitem', {
  96. name: 'main.tex',
  97. }).should('not.exist')
  98. // check that the confirmation modal is closed
  99. // is not, the 404 probably triggered a bug
  100. cy.findByText(
  101. 'Are you sure you want to permanently delete the following files?'
  102. ).should('not.exist')
  103. })
  104. it('aborts delete on error', function () {
  105. cy.intercept('delete', '/project/*/doc/*', { statusCode: 500 }).as(
  106. 'deleteDoc'
  107. )
  108. cy.findByRole('dialog').within(() => {
  109. cy.findByRole('button', { name: 'Delete' }).click()
  110. })
  111. // The modal should still be open, but the file should not be deleted
  112. cy.findByRole('treeitem', { name: 'main.tex', hidden: true })
  113. })
  114. })
  115. describe('folders', function () {
  116. let socket: SocketIOMock & Socket
  117. beforeEach(function () {
  118. socket = new SocketIOMock() as any
  119. const rootFolder = [
  120. {
  121. _id: 'root-folder-id',
  122. name: 'rootFolder',
  123. docs: [{ _id: '456def', name: 'main.tex' }],
  124. folders: [
  125. {
  126. _id: '123abc',
  127. name: 'folder',
  128. docs: [],
  129. folders: [],
  130. fileRefs: [{ _id: '789ghi', name: 'my.bib' }],
  131. },
  132. ],
  133. fileRefs: [],
  134. },
  135. ]
  136. cy.mount(
  137. <div style={{ width: 400 }}>
  138. <EditorProviders
  139. rootFolder={rootFolder as any}
  140. projectId="123abc"
  141. socket={socket}
  142. >
  143. <FileTreeRoot
  144. refProviders={{}}
  145. setRefProviderEnabled={cy.stub()}
  146. setStartedFreeTrial={cy.stub()}
  147. onSelect={cy.stub()}
  148. onInit={cy.stub()}
  149. isConnected
  150. />
  151. </EditorProviders>
  152. </div>
  153. )
  154. cy.findByRole('button', { name: 'Expand' }).click()
  155. cy.findByRole('treeitem', { name: 'main.tex' }).click()
  156. cy.findByRole('treeitem', { name: 'my.bib' }).click({
  157. ctrlKey: true,
  158. cmdKey: true,
  159. })
  160. cy.then(() => {
  161. socket.emitToClient('removeEntity', '123abc')
  162. })
  163. })
  164. it('removes the folder', function () {
  165. cy.findByRole('treeitem', { name: 'folder' }).should('not.exist')
  166. })
  167. it('leaves the main file selected', function () {
  168. cy.findByRole('treeitem', { name: 'main.tex', selected: true })
  169. })
  170. it('unselect the child entity', function () {
  171. // as a proxy to check that the child entity has been unselect we start
  172. // a delete and ensure the modal is displayed (the cancel button can be
  173. // selected) This is needed to make sure the test fail.
  174. cy.findByRole('button', { name: 'Open main.tex action menu' }).click()
  175. cy.findByRole('menuitem', { name: 'Delete' }).click()
  176. cy.findByRole('button', { name: 'Cancel' })
  177. })
  178. })
  179. describe('multiple entities', function () {
  180. let socket: SocketIOMock & Socket
  181. beforeEach(function () {
  182. socket = new SocketIOMock() as any
  183. const rootFolder = [
  184. {
  185. _id: 'root-folder-id',
  186. name: 'rootFolder',
  187. docs: [{ _id: '456def', name: 'main.tex' }],
  188. folders: [],
  189. fileRefs: [{ _id: '789ghi', name: 'my.bib' }],
  190. },
  191. ]
  192. cy.mount(
  193. <div style={{ width: 400 }}>
  194. <EditorProviders
  195. rootFolder={rootFolder as any}
  196. projectId="123abc"
  197. socket={socket}
  198. >
  199. <FileTreeRoot
  200. refProviders={{}}
  201. setRefProviderEnabled={cy.stub()}
  202. setStartedFreeTrial={cy.stub()}
  203. onSelect={cy.stub()}
  204. onInit={cy.stub()}
  205. isConnected
  206. />
  207. </EditorProviders>
  208. </div>
  209. )
  210. // select two files
  211. cy.findByRole('treeitem', { name: 'main.tex' }).click()
  212. cy.findByRole('treeitem', { name: 'my.bib' }).click({
  213. ctrlKey: true,
  214. cmdKey: true,
  215. })
  216. // open the context menu
  217. cy.findByRole('button', { name: 'my.bib' }).trigger('contextmenu')
  218. // make sure the menu has opened, with only a "Delete" item (as multiple files are selected)
  219. cy.findByRole('menu')
  220. cy.findAllByRole('menuitem').should('have.length', 1)
  221. // select the Delete menu item
  222. cy.findByRole('menuitem', { name: 'Delete' }).click()
  223. })
  224. it('removes all items and reindexes references after deleting .bib file', function () {
  225. cy.intercept('delete', '/project/123abc/doc/456def', {
  226. statusCode: 204,
  227. }).as('deleteDoc')
  228. cy.intercept('delete', '/project/123abc/file/789ghi', {
  229. statusCode: 204,
  230. }).as('deleteFile')
  231. cy.findByRole('dialog').within(() => {
  232. cy.findByRole('button', { name: 'Delete' }).click()
  233. })
  234. cy.then(() => {
  235. socket.emitToClient('removeEntity', '456def')
  236. socket.emitToClient('removeEntity', '789ghi')
  237. })
  238. for (const name of ['main.tex', 'my.bib']) {
  239. for (const hidden of [true, false]) {
  240. cy.findByRole('treeitem', { name, hidden }).should('not.exist')
  241. }
  242. }
  243. // check that the confirmation modal is closed
  244. cy.findByText('Are you sure').should('not.exist')
  245. cy.get('@deleteDoc.all').should('have.length', 1)
  246. cy.get('@deleteFile.all').should('have.length', 1)
  247. })
  248. })
  249. })