figure-modal.spec.tsx 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390
  1. import CodemirrorEditor from '../../../../../frontend/js/features/source-editor/components/codemirror-editor'
  2. import { EditorProviders } from '../../../helpers/editor-providers'
  3. import { mockScope, rootFolderId } from '../helpers/mock-scope'
  4. import { FC } from 'react'
  5. const Container: FC = ({ children }) => (
  6. <div style={{ width: 1500, height: 785 }}>{children}</div>
  7. )
  8. const clickToolbarButton = (text: string) => {
  9. cy.findByLabelText(text).click()
  10. cy.findByLabelText(text).trigger('mouseout')
  11. }
  12. const chooseFileFromComputer = () => {
  13. cy.get('@file-input').selectFile(
  14. {
  15. fileName: 'frog.jpg',
  16. contents: Cypress.Buffer.from('image-data'),
  17. mimeType: 'image/jpg',
  18. },
  19. {
  20. force: true,
  21. }
  22. )
  23. }
  24. const matchUrl = (urlToMatch: RegExp | string) =>
  25. Cypress.sinon.match(req => {
  26. if (!req || typeof req.url !== 'string') {
  27. return false
  28. }
  29. if (typeof urlToMatch === 'string') {
  30. return req.url.endsWith(urlToMatch)
  31. }
  32. return Boolean(req.url.match(urlToMatch))
  33. })
  34. describe('<FigureModal />', function () {
  35. // TODO: rewrite these tests to be in source mode when toolbar is added there
  36. // TODO: Write tests for width toggle, when we can match on source code
  37. beforeEach(function () {
  38. window.metaAttributesCache.set('ol-preventCompileOnLoad', true)
  39. window.metaAttributesCache.set('ol-splitTestVariants', {
  40. 'figure-modal': 'enabled',
  41. })
  42. window.metaAttributesCache.set(
  43. 'ol-mathJax3Path',
  44. 'https://unpkg.com/mathjax@3.2.2/es5/tex-svg-full.js'
  45. )
  46. cy.interceptEvents()
  47. cy.interceptSpelling()
  48. const content = ''
  49. const scope = mockScope(content)
  50. scope.editor.showVisual = true
  51. cy.mount(
  52. <Container>
  53. <EditorProviders scope={scope}>
  54. <CodemirrorEditor />
  55. </EditorProviders>
  56. </Container>
  57. )
  58. })
  59. describe('Upload from computer source', function () {
  60. beforeEach(function () {
  61. cy.interceptFileUpload()
  62. clickToolbarButton('Insert Figure')
  63. cy.findByRole('menu').within(() => {
  64. cy.findByText('Upload from computer').click()
  65. })
  66. cy.findByLabelText('File Uploader')
  67. .get('.uppy-Dashboard-input:first')
  68. .as('file-input')
  69. })
  70. it('Shows file name and size when selecting file', function () {
  71. chooseFileFromComputer()
  72. cy.findByLabelText('File name').should('have.text', 'frog.jpg')
  73. cy.findByLabelText('File size').should('have.text', '10 B')
  74. })
  75. it('Uploads file when clicking insert', function () {
  76. chooseFileFromComputer()
  77. cy.get('@uploadRequest').should('not.have.been.called')
  78. cy.findByText('Insert figure').click()
  79. cy.get('@uploadRequest').should(
  80. 'have.been.calledWith',
  81. matchUrl(`/project/test-project/upload?folder_id=${rootFolderId}`)
  82. )
  83. cy.get('.cm-content').should(
  84. 'have.text',
  85. '\\begin{figure} \\centering Enter Caption 🏷fig:enter-label\\end{figure}'
  86. )
  87. })
  88. it('Enables insert button when choosing file', function () {
  89. cy.findByText('Insert figure').should('be.disabled')
  90. chooseFileFromComputer()
  91. cy.findByText('Insert figure').should('be.enabled')
  92. })
  93. })
  94. describe('Upload from project files source', function () {
  95. beforeEach(function () {
  96. clickToolbarButton('Insert Figure')
  97. cy.findByRole('menu').within(() => {
  98. cy.findByText('From project files').click()
  99. })
  100. })
  101. it('Lists files from project', function () {
  102. cy.findByText('Select image from project files').click()
  103. cy.findByRole('listbox')
  104. .children()
  105. .should('have.length', 2)
  106. .should('contain.text', 'frog.jpg')
  107. .should('contain.text', 'unicorn.png')
  108. .should('contain.text', 'figures/')
  109. })
  110. it('Enables insert button when choosing file', function () {
  111. cy.findByText('Insert figure').should('be.disabled')
  112. cy.findByText('Select image from project files').click()
  113. cy.findByRole('listbox').within(() => {
  114. cy.findByText('frog.jpg').click()
  115. })
  116. cy.findByText('Insert figure').should('be.enabled')
  117. })
  118. it('Inserts file when pressing insert button', function () {
  119. cy.findByText('Select image from project files').click()
  120. cy.findByRole('listbox').within(() => {
  121. cy.findByText('frog.jpg').click()
  122. })
  123. cy.findByText('Insert figure').click()
  124. cy.get('.cm-content').should(
  125. 'have.text',
  126. '\\begin{figure} \\centering Enter Caption 🏷fig:enter-label\\end{figure}'
  127. )
  128. })
  129. })
  130. describe('From another project source', function () {
  131. beforeEach(function () {
  132. cy.interceptProjectListing()
  133. cy.interceptCompile()
  134. cy.interceptLinkedFile()
  135. clickToolbarButton('Insert Figure')
  136. cy.findByRole('menu').within(() => {
  137. cy.findByText('From another project').click()
  138. })
  139. cy.findByText('Select a project').parent().as('project-dropdown')
  140. cy.findByText('Select a file').parent().as('file-dropdown')
  141. })
  142. it('List projects and files in projects', function () {
  143. cy.findByText('Insert figure').should('be.disabled')
  144. cy.get('@file-dropdown').should('have.class', 'disabled')
  145. cy.get('@project-dropdown').click()
  146. cy.findByRole('listbox').as('project-select')
  147. cy.get('@project-select').children().should('have.length', 2)
  148. cy.get('@project-select').within(() => {
  149. cy.findByText('My first project').click()
  150. })
  151. cy.get('@file-dropdown').should('not.have.class', 'disabled')
  152. cy.get('@file-dropdown').click()
  153. cy.findByRole('listbox').as('file-select')
  154. cy.get('@file-select').children().should('have.length', 2)
  155. cy.get('@file-select').should('contain.text', 'frog.jpg')
  156. cy.get('@file-select').should('contain.text', 'figures/unicorn.png')
  157. cy.get('@file-select').within(() => {
  158. cy.findByText('frog.jpg').click()
  159. })
  160. cy.findByText('Insert figure').should('be.enabled')
  161. })
  162. it('Enables insert button when choosing file', function () {
  163. cy.findByText('Insert figure').should('be.disabled')
  164. cy.get('@project-dropdown').click()
  165. cy.findByRole('listbox').within(() => {
  166. cy.findByText('My first project').click()
  167. })
  168. cy.get('@file-dropdown').click()
  169. cy.findByRole('listbox').within(() => {
  170. cy.findByText('frog.jpg').click()
  171. })
  172. cy.findByText('Insert figure').should('be.enabled')
  173. })
  174. it('Creates linked file when pressing insert', function () {
  175. cy.get('@project-dropdown').click()
  176. cy.findByRole('listbox').within(() => {
  177. cy.findByText('My first project').click()
  178. })
  179. cy.get('@file-dropdown').click()
  180. cy.findByRole('listbox').within(() => {
  181. cy.findByText('frog.jpg').click()
  182. })
  183. cy.findByText('Insert figure').click()
  184. cy.get('@linked-file-request').should('have.been.calledWithMatch', {
  185. body: {
  186. provider: 'project_file',
  187. data: {
  188. source_entity_path: '/frog.jpg',
  189. source_project_id: 'fake-project-1',
  190. },
  191. },
  192. })
  193. cy.get('.cm-content').should(
  194. 'have.text',
  195. '\\begin{figure} \\centering Enter Caption 🏷fig:enter-label\\end{figure}'
  196. )
  197. })
  198. it('Creates linked output file when pressing insert', function () {
  199. cy.get('@project-dropdown').click()
  200. cy.findByRole('listbox').within(() => {
  201. cy.findByText('My first project').click()
  202. })
  203. cy.findByText('select from output files').click()
  204. cy.findByText('Select an output file').parent().as('output-file-dropdown')
  205. cy.get('@output-file-dropdown').click()
  206. cy.findByRole('listbox').within(() => {
  207. cy.findByText('output.pdf').click()
  208. })
  209. cy.findByText('Insert figure').click()
  210. cy.get('@linked-file-request').should('have.been.calledWithMatch', {
  211. body: {
  212. provider: 'project_output_file',
  213. data: {
  214. source_output_file_path: 'output.pdf',
  215. source_project_id: 'fake-project-1',
  216. },
  217. },
  218. })
  219. cy.get('.cm-content').should(
  220. 'have.text',
  221. '\\begin{figure} \\centering Enter Caption 🏷fig:enter-label\\end{figure}'
  222. )
  223. })
  224. })
  225. describe('From URL source', function () {
  226. beforeEach(function () {
  227. cy.interceptLinkedFile()
  228. clickToolbarButton('Insert Figure')
  229. cy.findByRole('menu').within(() => {
  230. cy.findByText('From URL').click()
  231. })
  232. cy.findByLabelText('File name in this project').as(
  233. 'relocated-file-name-input'
  234. )
  235. cy.findByLabelText('Image URL').as('image-url-input')
  236. cy.get('[data-cy="include-label-option"]').as('include-label-checkbox')
  237. cy.get('[data-cy="include-caption-option"]').as(
  238. 'include-caption-checkbox'
  239. )
  240. })
  241. it('Auto fills name based on url', function () {
  242. cy.get('@image-url-input').type('https://my-fake-website.com/frog.jpg')
  243. cy.get('@relocated-file-name-input').should('have.value', 'frog.jpg')
  244. cy.get('@relocated-file-name-input').type('pig')
  245. cy.get('@relocated-file-name-input').should('have.value', 'pig.jpg')
  246. })
  247. it('Enables insert button when name and url is available', function () {
  248. cy.findByText('Insert figure').should('be.disabled')
  249. cy.get('@image-url-input').type('https://my-fake-website.com/frog.jpg')
  250. cy.findByText('Insert figure').should('be.enabled')
  251. })
  252. it('Adds linked file when pressing insert', function () {
  253. cy.get('@image-url-input').type('https://my-fake-website.com/frog.jpg')
  254. cy.findByText('Insert figure').click()
  255. cy.get('@linked-file-request').should('have.been.calledWithMatch', {
  256. body: {
  257. provider: 'url',
  258. data: {
  259. url: 'https://my-fake-website.com/frog.jpg',
  260. },
  261. },
  262. })
  263. cy.get('.cm-content').should(
  264. 'have.text',
  265. '\\begin{figure} \\centering Enter Caption 🏷fig:enter-label\\end{figure}'
  266. )
  267. })
  268. })
  269. describe('Editing existing figure', function () {
  270. it('Parses existing label and caption', function () {
  271. cy.get('.cm-content').type(
  272. `\\begin{{}figure}
  273. \\centering
  274. \\includegraphics[width=0.5\\linewidth]{{}frog.jpg}
  275. \\caption{{}My caption}
  276. \\label{{}fig:my-label}
  277. \\end{{}figure}`,
  278. { delay: 0 }
  279. )
  280. cy.get('[aria-label="Edit figure"]').click()
  281. cy.get('[data-cy="include-caption-option"]').should('be.checked')
  282. cy.get('[data-cy="include-label-option"]').should('be.checked')
  283. })
  284. it('Parses existing width', function () {
  285. cy.get('.cm-content').type(
  286. `\\begin{{}figure}
  287. \\centering
  288. \\includegraphics[width=0.75\\linewidth]{{}frog.jpg}
  289. \\caption{{}My caption}
  290. \\label{{}fig:my-label}
  291. \\end{{}figure}`,
  292. { delay: 0 }
  293. )
  294. cy.get('[aria-label="Edit figure"]').click()
  295. cy.get('[value="0.75"]').should('be.checked')
  296. })
  297. it('Removes existing label when unchecked', function () {
  298. cy.get('.cm-content').type(
  299. `\\begin{{}figure}
  300. \\centering
  301. \\includegraphics[width=0.75\\linewidth]{{}frog.jpg}
  302. \\label{{}fig:my-label}
  303. \\end{{}figure}`,
  304. { delay: 0 }
  305. )
  306. cy.get('[aria-label="Edit figure"]').click()
  307. cy.get('[data-cy="include-label-option"]').click()
  308. cy.get('[data-cy="include-label-option"]').should('not.be.checked')
  309. cy.findByText('Done').click()
  310. cy.get('.cm-content').should(
  311. 'have.text',
  312. '\\begin{figure}\\centering\\end{figure}'
  313. )
  314. })
  315. it('Removes existing caption when unchecked', function () {
  316. cy.get('.cm-content').type(
  317. `\\begin{{}figure}
  318. \\centering
  319. \\includegraphics[width=0.75\\linewidth]{{}frog.jpg}
  320. \\caption{{}My caption}
  321. \\label{{}fig:my-label}
  322. \\end{{}figure}`,
  323. { delay: 0 }
  324. )
  325. cy.get('[aria-label="Edit figure"]').click()
  326. cy.get('[data-cy="include-caption-option"]').click()
  327. cy.get('[data-cy="include-caption-option"]').should('not.be.checked')
  328. cy.findByText('Done').click()
  329. cy.get('.cm-content').should(
  330. 'have.text',
  331. '\\begin{figure}\\centering🏷fig:my-label\\end{figure}'
  332. )
  333. })
  334. it('Preserves other content when removing figure', function () {
  335. cy.get('.cm-content').type(
  336. `text above
  337. \\begin{{}figure}
  338. \\centering
  339. \\includegraphics[width=0.75\\linewidth]{{}frog.jpg}
  340. \\caption{{}My caption}
  341. \\label{{}fig:my-label}
  342. \\end{{}figure}
  343. text below`,
  344. { delay: 0 }
  345. )
  346. cy.get('[aria-label="Edit figure"]').click()
  347. cy.get('[aria-label="Remove or replace figure"]').click()
  348. cy.findByText('Delete figure').click()
  349. cy.get('.cm-content').should('have.text', 'text abovetext below')
  350. })
  351. // TODO: Add tests for replacing image when we can match on image path
  352. // TODO: Add tests for changing image size when we can match on figure width
  353. })
  354. })