file-tree-root.test.js 8.2 KB

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