file-tree-root.test.js 8.0 KB

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