file-tree-root.test.js 8.0 KB

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