file-tree-root.spec.tsx 11 KB

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