file-tree-root.test.js 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307
  1. import { expect } from 'chai'
  2. import sinon from 'sinon'
  3. import { screen, fireEvent, waitFor } from '@testing-library/react'
  4. import fetchMock from 'fetch-mock'
  5. import {
  6. renderWithEditorContext,
  7. cleanUpContext,
  8. } from '../../../helpers/render-with-context'
  9. import FileTreeRoot from '../../../../../frontend/js/features/file-tree/components/file-tree-root'
  10. describe('<FileTreeRoot/>', function () {
  11. const onSelect = sinon.stub()
  12. const onInit = sinon.stub()
  13. beforeEach(function () {
  14. global.requestAnimationFrame = sinon.stub()
  15. })
  16. afterEach(function () {
  17. delete global.requestAnimationFrame
  18. fetchMock.restore()
  19. onSelect.reset()
  20. onInit.reset()
  21. cleanUpContext()
  22. global.localStorage.clear()
  23. })
  24. it('renders', function () {
  25. const rootFolder = [
  26. {
  27. _id: 'root-folder-id',
  28. name: 'rootFolder',
  29. docs: [{ _id: '456def', name: 'main.tex' }],
  30. folders: [],
  31. fileRefs: [],
  32. },
  33. ]
  34. const { container } = renderWithEditorContext(
  35. <FileTreeRoot
  36. refProviders={{}}
  37. reindexReferences={() => null}
  38. setRefProviderEnabled={() => null}
  39. setStartedFreeTrial={() => null}
  40. onSelect={onSelect}
  41. onInit={onInit}
  42. isConnected
  43. />,
  44. {
  45. projectRootFolder: rootFolder,
  46. projectId: '123abc',
  47. rootDocId: '456def',
  48. features: {},
  49. permissionsLevel: 'owner',
  50. }
  51. )
  52. screen.queryByRole('tree')
  53. screen.getByRole('treeitem')
  54. screen.getByRole('treeitem', { name: 'main.tex', selected: true })
  55. expect(container.querySelector('.disconnected-overlay')).to.not.exist
  56. })
  57. it('renders with invalid selected doc in local storage', async function () {
  58. global.localStorage.setItem(
  59. 'doc.open_id.123abc',
  60. JSON.stringify('not-a-valid-id')
  61. )
  62. const rootFolder = [
  63. {
  64. _id: 'root-folder-id',
  65. name: 'rootFolder',
  66. docs: [{ _id: '456def', name: 'main.tex' }],
  67. folders: [],
  68. fileRefs: [],
  69. },
  70. ]
  71. renderWithEditorContext(
  72. <FileTreeRoot
  73. refProviders={{}}
  74. reindexReferences={() => null}
  75. setRefProviderEnabled={() => null}
  76. setStartedFreeTrial={() => null}
  77. onSelect={onSelect}
  78. onInit={onInit}
  79. isConnected
  80. />,
  81. {
  82. projectRootFolder: rootFolder,
  83. projectId: '123abc',
  84. rootDocId: '456def',
  85. features: {},
  86. permissionsLevel: 'owner',
  87. }
  88. )
  89. // as a proxy to check that the invalid entity ha not been select we start
  90. // a delete and ensure the modal is displayed (the cancel button can be
  91. // selected) This is needed to make sure the test fail.
  92. const treeitemFile = screen.getByRole('treeitem', { name: 'main.tex' })
  93. fireEvent.click(treeitemFile, { ctrlKey: true })
  94. const toggleButton = screen.getByRole('button', { name: 'Menu' })
  95. fireEvent.click(toggleButton)
  96. const deleteButton = screen.getByRole('menuitem', { name: 'Delete' })
  97. fireEvent.click(deleteButton)
  98. await waitFor(() => screen.getByRole('button', { name: 'Cancel' }))
  99. })
  100. it('renders disconnected overlay', function () {
  101. const rootFolder = [
  102. {
  103. _id: 'root-folder-id',
  104. name: 'rootFolder',
  105. docs: [{ _id: '456def', name: 'main.tex' }],
  106. folders: [],
  107. fileRefs: [],
  108. },
  109. ]
  110. const { container } = renderWithEditorContext(
  111. <FileTreeRoot
  112. onSelect={onSelect}
  113. onInit={onInit}
  114. isConnected={false}
  115. refProviders={{}}
  116. reindexReferences={() => null}
  117. setRefProviderEnabled={() => null}
  118. setStartedFreeTrial={() => null}
  119. />,
  120. {
  121. projectRootFolder: rootFolder,
  122. projectId: '123abc',
  123. rootDocId: '456def',
  124. features: {},
  125. permissionsLevel: 'owner',
  126. }
  127. )
  128. expect(container.querySelector('.disconnected-overlay')).to.exist
  129. })
  130. it('fire onSelect', function () {
  131. const rootFolder = [
  132. {
  133. _id: 'root-folder-id',
  134. name: 'rootFolder',
  135. docs: [
  136. { _id: '456def', name: 'main.tex' },
  137. { _id: '789ghi', name: 'other.tex' },
  138. ],
  139. folders: [],
  140. fileRefs: [],
  141. },
  142. ]
  143. renderWithEditorContext(
  144. <FileTreeRoot
  145. refProviders={{}}
  146. reindexReferences={() => null}
  147. setRefProviderEnabled={() => null}
  148. setStartedFreeTrial={() => null}
  149. onSelect={onSelect}
  150. onInit={onInit}
  151. isConnected
  152. />,
  153. {
  154. projectRootFolder: rootFolder,
  155. projectId: '123abc',
  156. rootDocId: '456def',
  157. features: {},
  158. permissionsLevel: 'readOnly',
  159. }
  160. )
  161. sinon.assert.calledOnce(onSelect)
  162. sinon.assert.calledWithMatch(onSelect, [
  163. sinon.match({
  164. entity: {
  165. _id: '456def',
  166. name: 'main.tex',
  167. },
  168. }),
  169. ])
  170. onSelect.reset()
  171. screen.queryByRole('tree')
  172. const treeitem = screen.getByRole('treeitem', { name: 'other.tex' })
  173. fireEvent.click(treeitem)
  174. sinon.assert.calledOnce(onSelect)
  175. sinon.assert.calledWithMatch(onSelect, [
  176. sinon.match({
  177. entity: {
  178. _id: '789ghi',
  179. name: 'other.tex',
  180. },
  181. }),
  182. ])
  183. })
  184. it('listen to editor.openDoc', function () {
  185. const rootFolder = [
  186. {
  187. _id: 'root-folder-id',
  188. name: 'rootFolder',
  189. docs: [
  190. { _id: '456def', name: 'main.tex' },
  191. { _id: '789ghi', name: 'other.tex' },
  192. ],
  193. folders: [],
  194. fileRefs: [],
  195. },
  196. ]
  197. renderWithEditorContext(
  198. <FileTreeRoot
  199. refProviders={{}}
  200. reindexReferences={() => null}
  201. setRefProviderEnabled={() => null}
  202. setStartedFreeTrial={() => null}
  203. onSelect={onSelect}
  204. onInit={onInit}
  205. isConnected
  206. />,
  207. {
  208. projectRootFolder: rootFolder,
  209. projectId: '123abc',
  210. rootDocId: '456def',
  211. features: {},
  212. permissionsLevel: 'owner',
  213. }
  214. )
  215. screen.getByRole('treeitem', { name: 'main.tex', selected: true })
  216. // entities not found should be ignored
  217. window.dispatchEvent(
  218. new CustomEvent('editor.openDoc', { detail: 'not-an-id' })
  219. )
  220. screen.getByRole('treeitem', { name: 'main.tex', selected: true })
  221. window.dispatchEvent(
  222. new CustomEvent('editor.openDoc', { detail: '789ghi' })
  223. )
  224. screen.getByRole('treeitem', { name: 'main.tex', selected: false })
  225. screen.getByRole('treeitem', { name: 'other.tex', selected: true })
  226. })
  227. it('only shows a menu button when a single item is selected', function () {
  228. const rootFolder = [
  229. {
  230. _id: 'root-folder-id',
  231. name: 'rootFolder',
  232. docs: [
  233. { _id: '456def', name: 'main.tex' },
  234. { _id: '789ghi', name: 'other.tex' },
  235. ],
  236. folders: [],
  237. fileRefs: [],
  238. },
  239. ]
  240. renderWithEditorContext(
  241. <FileTreeRoot
  242. refProviders={{}}
  243. reindexReferences={() => null}
  244. setRefProviderEnabled={() => null}
  245. setStartedFreeTrial={() => null}
  246. onSelect={onSelect}
  247. onInit={onInit}
  248. isConnected
  249. />,
  250. {
  251. projectRootFolder: rootFolder,
  252. projectId: '123abc',
  253. rootDocId: '456def',
  254. features: {},
  255. permissionsLevel: 'owner',
  256. }
  257. )
  258. const main = screen.getByRole('treeitem', {
  259. name: 'main.tex',
  260. selected: true,
  261. })
  262. const other = screen.getByRole('treeitem', {
  263. name: 'other.tex',
  264. selected: false,
  265. })
  266. // single item selected: menu button is visible
  267. expect(screen.queryAllByRole('button', { name: 'Menu' })).to.have.length(1)
  268. // select the other item
  269. fireEvent.click(other)
  270. screen.getByRole('treeitem', { name: 'main.tex', selected: false })
  271. screen.getByRole('treeitem', { name: 'other.tex', selected: true })
  272. // single item selected: menu button is visible
  273. expect(screen.queryAllByRole('button', { name: 'Menu' })).to.have.length(1)
  274. // multi-select the main item
  275. fireEvent.click(main, { ctrlKey: true })
  276. screen.getByRole('treeitem', { name: 'main.tex', selected: true })
  277. screen.getByRole('treeitem', { name: 'other.tex', selected: true })
  278. // multiple items selected: no menu button is visible
  279. expect(screen.queryAllByRole('button', { name: 'Menu' })).to.have.length(0)
  280. })
  281. })