file-tree-root.spec.tsx 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390
  1. import '../../../helpers/bootstrap-3'
  2. import FileTreeRoot from '../../../../../frontend/js/features/file-tree/components/file-tree-root'
  3. import { EditorProviders } from '../../../helpers/editor-providers'
  4. import { SocketIOMock } from '@/ide/connection/SocketIoShim'
  5. describe('<FileTreeRoot/>', function () {
  6. beforeEach(function () {
  7. cy.window().then(win => {
  8. win.metaAttributesCache.set('ol-user', { id: 'user1' })
  9. })
  10. })
  11. it('renders', function () {
  12. const rootFolder = [
  13. {
  14. _id: 'root-folder-id',
  15. name: 'rootFolder',
  16. docs: [{ _id: '456def', name: 'main.tex' }],
  17. folders: [],
  18. fileRefs: [],
  19. },
  20. ]
  21. cy.mount(
  22. <EditorProviders
  23. rootFolder={rootFolder as any}
  24. projectId="123abc"
  25. rootDocId="456def"
  26. features={{} as any}
  27. permissionsLevel="owner"
  28. >
  29. <FileTreeRoot
  30. refProviders={{}}
  31. setRefProviderEnabled={cy.stub()}
  32. setStartedFreeTrial={cy.stub()}
  33. onSelect={cy.stub()}
  34. onInit={cy.stub()}
  35. isConnected
  36. />
  37. </EditorProviders>
  38. )
  39. cy.findByRole('tree')
  40. cy.findByRole('treeitem')
  41. cy.findByRole('treeitem', { name: 'main.tex', selected: true })
  42. cy.get('.disconnected-overlay').should('not.exist')
  43. })
  44. it('renders with invalid selected doc in local storage', function () {
  45. global.localStorage.setItem(
  46. 'doc.open_id.123abc',
  47. JSON.stringify('not-a-valid-id')
  48. )
  49. const rootFolder = [
  50. {
  51. _id: 'root-folder-id',
  52. name: 'rootFolder',
  53. docs: [{ _id: '456def', name: 'main.tex' }],
  54. folders: [],
  55. fileRefs: [],
  56. },
  57. ]
  58. cy.mount(
  59. <div style={{ width: 400 }}>
  60. <EditorProviders
  61. rootFolder={rootFolder as any}
  62. projectId="123abc"
  63. rootDocId="456def"
  64. features={{} as any}
  65. permissionsLevel="owner"
  66. >
  67. <FileTreeRoot
  68. refProviders={{}}
  69. setRefProviderEnabled={cy.stub()}
  70. setStartedFreeTrial={cy.stub()}
  71. onSelect={cy.stub()}
  72. onInit={cy.stub()}
  73. isConnected
  74. />
  75. </EditorProviders>
  76. </div>
  77. )
  78. // as a proxy to check that the invalid entity has not been select we start
  79. // a delete and ensure the modal is displayed (the cancel button can be
  80. // selected) This is needed to make sure the test fail.
  81. cy.findByRole('treeitem', { name: 'main.tex' }).click({
  82. ctrlKey: true,
  83. cmdKey: true,
  84. })
  85. cy.findByRole('button', { name: 'Menu' }).click()
  86. cy.findByRole('menuitem', { name: 'Delete' }).click()
  87. cy.findByRole('button', { name: 'Cancel' })
  88. })
  89. it('renders disconnected overlay', function () {
  90. const rootFolder = [
  91. {
  92. _id: 'root-folder-id',
  93. name: 'rootFolder',
  94. docs: [{ _id: '456def', name: 'main.tex' }],
  95. folders: [],
  96. fileRefs: [],
  97. },
  98. ]
  99. cy.mount(
  100. <EditorProviders
  101. rootFolder={rootFolder as any}
  102. projectId="123abc"
  103. rootDocId="456def"
  104. features={{} as any}
  105. permissionsLevel="owner"
  106. >
  107. <FileTreeRoot
  108. refProviders={{}}
  109. setRefProviderEnabled={cy.stub()}
  110. setStartedFreeTrial={cy.stub()}
  111. onSelect={cy.stub()}
  112. onInit={cy.stub()}
  113. isConnected={false}
  114. />
  115. </EditorProviders>
  116. )
  117. cy.get('.disconnected-overlay')
  118. })
  119. it('fire onSelect', function () {
  120. const rootFolder = [
  121. {
  122. _id: 'root-folder-id',
  123. name: 'rootFolder',
  124. docs: [
  125. { _id: '456def', name: 'main.tex' },
  126. { _id: '789ghi', name: 'other.tex' },
  127. ],
  128. folders: [],
  129. fileRefs: [],
  130. },
  131. ]
  132. cy.mount(
  133. <EditorProviders
  134. rootFolder={rootFolder as any}
  135. projectId="123abc"
  136. rootDocId="456def"
  137. features={{} as any}
  138. permissionsLevel="readOnly"
  139. >
  140. <FileTreeRoot
  141. refProviders={{}}
  142. setRefProviderEnabled={cy.stub()}
  143. setStartedFreeTrial={cy.stub()}
  144. onSelect={cy.stub().as('onSelect')}
  145. onInit={cy.stub()}
  146. isConnected
  147. />
  148. </EditorProviders>
  149. )
  150. cy.get('@onSelect').should('have.been.calledOnceWith', [
  151. Cypress.sinon.match({
  152. entity: Cypress.sinon.match({ _id: '456def', name: 'main.tex' }),
  153. }),
  154. ])
  155. cy.findByRole('tree')
  156. cy.findByRole('treeitem', { name: 'other.tex' }).click()
  157. cy.get('@onSelect').should('have.been.calledWith', [
  158. Cypress.sinon.match({
  159. entity: Cypress.sinon.match({ _id: '789ghi', name: 'other.tex' }),
  160. }),
  161. ])
  162. })
  163. it('listen to editor.openDoc', function () {
  164. const rootFolder = [
  165. {
  166. _id: 'root-folder-id',
  167. name: 'rootFolder',
  168. docs: [
  169. { _id: '456def', name: 'main.tex' },
  170. { _id: '789ghi', name: 'other.tex' },
  171. ],
  172. folders: [],
  173. fileRefs: [],
  174. },
  175. ]
  176. cy.mount(
  177. <EditorProviders
  178. rootFolder={rootFolder as any}
  179. projectId="123abc"
  180. rootDocId="456def"
  181. features={{} as any}
  182. permissionsLevel="readOnly"
  183. >
  184. <FileTreeRoot
  185. refProviders={{}}
  186. setRefProviderEnabled={cy.stub()}
  187. setStartedFreeTrial={cy.stub()}
  188. onSelect={cy.stub()}
  189. onInit={cy.stub()}
  190. isConnected
  191. />
  192. </EditorProviders>
  193. )
  194. cy.findByRole('treeitem', { name: 'main.tex', selected: true })
  195. // entities not found should be ignored
  196. cy.document().trigger('editor.openDoc', { detail: 'not-an-id' })
  197. cy.findByRole('treeitem', { name: 'main.tex', selected: true })
  198. cy.document().trigger('editor.openDoc', { detail: '789ghi' })
  199. cy.findByRole('treeitem', { name: 'main.tex', selected: false })
  200. cy.findByRole('treeitem', { name: 'other.tex', selected: true })
  201. })
  202. it('only shows a menu button when a single item is selected', function () {
  203. const rootFolder = [
  204. {
  205. _id: 'root-folder-id',
  206. name: 'rootFolder',
  207. docs: [
  208. { _id: '456def', name: 'main.tex' },
  209. { _id: '789ghi', name: 'other.tex' },
  210. ],
  211. folders: [],
  212. fileRefs: [],
  213. },
  214. ]
  215. cy.mount(
  216. <EditorProviders
  217. rootFolder={rootFolder as any}
  218. projectId="123abc"
  219. rootDocId="456def"
  220. features={{} as any}
  221. permissionsLevel="owner"
  222. >
  223. <FileTreeRoot
  224. refProviders={{}}
  225. setRefProviderEnabled={cy.stub()}
  226. setStartedFreeTrial={cy.stub()}
  227. onSelect={cy.stub()}
  228. onInit={cy.stub()}
  229. isConnected
  230. />
  231. </EditorProviders>
  232. )
  233. cy.findByRole('treeitem', { name: 'main.tex', selected: true })
  234. cy.findByRole('treeitem', { name: 'other.tex', selected: false })
  235. // single item selected: menu button is visible
  236. cy.findAllByRole('button', { name: 'Menu' }).should('have.length', 1)
  237. // select the other item
  238. cy.findByRole('treeitem', { name: 'other.tex' }).click()
  239. cy.findByRole('treeitem', { name: 'main.tex', selected: false })
  240. cy.findByRole('treeitem', { name: 'other.tex', selected: true })
  241. // single item selected: menu button is visible
  242. cy.findAllByRole('button', { name: 'Menu' }).should('have.length', 1)
  243. // multi-select the main item
  244. cy.findByRole('treeitem', { name: 'main.tex' }).click({
  245. ctrlKey: true,
  246. cmdKey: true,
  247. })
  248. cy.findByRole('treeitem', { name: 'main.tex', selected: true })
  249. cy.findByRole('treeitem', { name: 'other.tex', selected: true })
  250. // multiple items selected: no menu button is visible
  251. cy.findAllByRole('button', { name: 'Menu' }).should('have.length', 0)
  252. })
  253. describe('when deselecting files', function () {
  254. let socket: SocketIOMock
  255. beforeEach(function () {
  256. socket = new SocketIOMock()
  257. const rootFolder = [
  258. {
  259. _id: 'root-folder-id',
  260. name: 'rootFolder',
  261. docs: [{ _id: '123abc', name: 'main.tex' }],
  262. folders: [
  263. {
  264. _id: '789ghi',
  265. name: 'thefolder',
  266. docs: [{ _id: '456def', name: 'sub.tex' }],
  267. fileRefs: [],
  268. folders: [],
  269. },
  270. ],
  271. fileRefs: [],
  272. },
  273. ]
  274. cy.mount(
  275. <EditorProviders
  276. rootFolder={rootFolder as any}
  277. projectId="123abc"
  278. rootDocId="456def"
  279. features={{} as any}
  280. permissionsLevel="owner"
  281. socket={socket}
  282. >
  283. <FileTreeRoot
  284. refProviders={{}}
  285. setRefProviderEnabled={cy.stub()}
  286. setStartedFreeTrial={cy.stub()}
  287. onSelect={cy.stub()}
  288. onInit={cy.stub()}
  289. isConnected
  290. />
  291. </EditorProviders>
  292. )
  293. // select the sub file
  294. cy.findByRole('treeitem', { name: 'sub.tex' }).click()
  295. cy.findByRole('treeitem', { name: 'sub.tex' }).should(
  296. 'have.attr',
  297. 'aria-selected',
  298. 'true'
  299. )
  300. // click on empty area (after giving it extra height below the tree)
  301. cy.findByTestId('file-tree-inner')
  302. .invoke('attr', 'style', 'height: 400px')
  303. .click()
  304. })
  305. it('removes the selected indicator', function () {
  306. cy.findByRole('treeitem', { selected: true }).should('not.exist')
  307. })
  308. it('disables the "rename" and "delete" buttons', function () {
  309. cy.findByRole('button', { name: 'Rename' }).should('not.exist')
  310. cy.findByRole('button', { name: 'Delete' }).should('not.exist')
  311. })
  312. it('creates new file in the root folder', function () {
  313. cy.intercept('project/*/doc', { statusCode: 200 })
  314. cy.findByRole('button', { name: /new file/i }).click()
  315. cy.findByRole('button', { name: /create/i }).click()
  316. cy.then(() => {
  317. socket.emitToClient('reciveNewDoc', 'root-folder-id', {
  318. _id: '12345',
  319. name: 'abcdef.tex',
  320. docs: [],
  321. fileRefs: [],
  322. folders: [],
  323. })
  324. })
  325. cy.findByRole('treeitem', { name: 'abcdef.tex' }).then($itemEl => {
  326. cy.findByTestId('file-tree-list-root').then($rootEl => {
  327. expect($itemEl.get(0).parentNode).to.equal($rootEl.get(0))
  328. })
  329. })
  330. })
  331. it('starts a new selection', function () {
  332. cy.findByRole('treeitem', { name: 'sub.tex' }).should(
  333. 'have.attr',
  334. 'aria-selected',
  335. 'false'
  336. )
  337. cy.findByRole('treeitem', { name: 'main.tex' }).click({
  338. ctrlKey: true,
  339. cmdKey: true,
  340. })
  341. cy.findByRole('treeitem', { name: 'main.tex' }).should(
  342. 'have.attr',
  343. 'aria-selected',
  344. 'true'
  345. )
  346. })
  347. })
  348. })