file-tree-root.test.js 8.3 KB

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