file-tree-root.spec.tsx 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399
  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: 'Open main.tex action 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: 'Open main.tex action menu' }).should(
  237. 'have.length',
  238. 1
  239. )
  240. // select the other item
  241. cy.findByRole('treeitem', { name: 'other.tex' }).click()
  242. cy.findByRole('treeitem', { name: 'main.tex', selected: false })
  243. cy.findByRole('treeitem', { name: 'other.tex', selected: true })
  244. // single item selected: menu button is visible
  245. cy.findAllByRole('button', { name: 'Open other.tex action menu' }).should(
  246. 'have.length',
  247. 1
  248. )
  249. // multi-select the main item
  250. cy.findByRole('treeitem', { name: 'main.tex' }).click({
  251. ctrlKey: true,
  252. cmdKey: true,
  253. })
  254. cy.findByRole('treeitem', { name: 'main.tex', selected: true })
  255. cy.findByRole('treeitem', { name: 'other.tex', selected: true })
  256. // multiple items selected: no menu button is visible
  257. cy.findAllByRole('button', { name: 'Open main.tex action menu' }).should(
  258. 'have.length',
  259. 0
  260. )
  261. })
  262. describe('when deselecting files', function () {
  263. let socket: SocketIOMock
  264. beforeEach(function () {
  265. socket = new SocketIOMock()
  266. const rootFolder = [
  267. {
  268. _id: 'root-folder-id',
  269. name: 'rootFolder',
  270. docs: [{ _id: '123abc', name: 'main.tex' }],
  271. folders: [
  272. {
  273. _id: '789ghi',
  274. name: 'thefolder',
  275. docs: [{ _id: '456def', name: 'sub.tex' }],
  276. fileRefs: [],
  277. folders: [],
  278. },
  279. ],
  280. fileRefs: [],
  281. },
  282. ]
  283. cy.mount(
  284. <EditorProviders
  285. rootFolder={rootFolder as any}
  286. projectId="123abc"
  287. rootDocId="456def"
  288. features={{} as any}
  289. permissionsLevel="owner"
  290. socket={socket}
  291. >
  292. <FileTreeRoot
  293. refProviders={{}}
  294. setRefProviderEnabled={cy.stub()}
  295. setStartedFreeTrial={cy.stub()}
  296. onSelect={cy.stub()}
  297. onInit={cy.stub()}
  298. isConnected
  299. />
  300. </EditorProviders>
  301. )
  302. // select the sub file
  303. cy.findByRole('treeitem', { name: 'sub.tex' }).click()
  304. cy.findByRole('treeitem', { name: 'sub.tex' }).should(
  305. 'have.attr',
  306. 'aria-selected',
  307. 'true'
  308. )
  309. // click on empty area (after giving it extra height below the tree)
  310. cy.findByTestId('file-tree-inner')
  311. .invoke('attr', 'style', 'height: 400px')
  312. .click()
  313. })
  314. it('removes the selected indicator', function () {
  315. cy.findByRole('treeitem', { selected: true }).should('not.exist')
  316. })
  317. it('disables the "rename" and "delete" buttons', function () {
  318. cy.findByRole('button', { name: 'Rename' }).should('not.exist')
  319. cy.findByRole('button', { name: 'Delete' }).should('not.exist')
  320. })
  321. it('creates new file in the root folder', function () {
  322. cy.intercept('project/*/doc', { statusCode: 200 })
  323. cy.findByRole('button', { name: /new file/i }).click()
  324. cy.findByRole('button', { name: /create/i }).click()
  325. cy.then(() => {
  326. socket.emitToClient('reciveNewDoc', 'root-folder-id', {
  327. _id: '12345',
  328. name: 'abcdef.tex',
  329. docs: [],
  330. fileRefs: [],
  331. folders: [],
  332. })
  333. })
  334. cy.findByRole('treeitem', { name: 'abcdef.tex' }).then($itemEl => {
  335. cy.findByTestId('file-tree-list-root').then($rootEl => {
  336. expect($itemEl.get(0).parentNode?.parentNode).to.equal($rootEl.get(0))
  337. })
  338. })
  339. })
  340. it('starts a new selection', function () {
  341. cy.findByRole('treeitem', { name: 'sub.tex' }).should(
  342. 'have.attr',
  343. 'aria-selected',
  344. 'false'
  345. )
  346. cy.findByRole('treeitem', { name: 'main.tex' }).click({
  347. ctrlKey: true,
  348. cmdKey: true,
  349. })
  350. cy.findByRole('treeitem', { name: 'main.tex' }).should(
  351. 'have.attr',
  352. 'aria-selected',
  353. 'true'
  354. )
  355. })
  356. })
  357. })