figure-modal.spec.tsx 13 KB

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