file-tree-root.spec.tsx 11 KB

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