editor.spec.ts 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239
  1. import {
  2. createNewFile,
  3. createProject,
  4. openProjectById,
  5. testNewFileUpload,
  6. } from './helpers/project'
  7. import { isExcludedBySharding, startWith } from './helpers/config'
  8. import { ensureUserExists, login } from './helpers/login'
  9. import { v4 as uuid } from 'uuid'
  10. import { beforeWithReRunOnTestRetry } from './helpers/beforeWithReRunOnTestRetry'
  11. import { prepareWaitForNextCompileSlot } from './helpers/compile'
  12. describe('editor', () => {
  13. if (isExcludedBySharding('PRO_DEFAULT_1')) return
  14. startWith({ pro: true })
  15. ensureUserExists({ email: 'user@example.com' })
  16. ensureUserExists({ email: 'collaborator@example.com' })
  17. let projectName: string
  18. let projectId: string
  19. let recompile: () => void
  20. let waitForCompileRateLimitCoolOff: (fn: () => void) => void
  21. beforeWithReRunOnTestRetry(function () {
  22. projectName = `project-${uuid()}`
  23. login('user@example.com')
  24. createProject(projectName, { type: 'Example project', open: false }).then(
  25. id => (projectId = id)
  26. )
  27. ;({ recompile, waitForCompileRateLimitCoolOff } =
  28. prepareWaitForNextCompileSlot())
  29. })
  30. beforeEach(() => {
  31. login('user@example.com')
  32. waitForCompileRateLimitCoolOff(() => {
  33. openProjectById(projectId)
  34. })
  35. })
  36. describe('spelling', function () {
  37. function changeSpellCheckLanguageTo(lng: string) {
  38. cy.log(`change project language to '${lng}'`)
  39. cy.get('button').contains('Menu').click()
  40. cy.get('select[id=settings-menu-spellCheckLanguage]').select(lng)
  41. cy.get('[id="left-menu"]').type('{esc}') // close left menu
  42. }
  43. afterEach(function () {
  44. changeSpellCheckLanguageTo('Off')
  45. })
  46. it('word dictionary and spelling', () => {
  47. changeSpellCheckLanguageTo('English (American)')
  48. createNewFile()
  49. const word = createRandomLetterString()
  50. cy.log('edit project file')
  51. cy.get('.cm-line').type(word)
  52. cy.findByText(word).should('have.class', 'ol-cm-spelling-error')
  53. changeSpellCheckLanguageTo('Off')
  54. cy.findByText(word).should('not.have.class', 'ol-cm-spelling-error')
  55. changeSpellCheckLanguageTo('Spanish')
  56. cy.findByText(word).should('have.class', 'ol-cm-spelling-error')
  57. cy.log('add word to dictionary')
  58. cy.findByText(word).rightclick()
  59. cy.findByRole('menuitem', { name: 'Add to dictionary' }).click()
  60. cy.findByText(word).should('not.have.class', 'ol-cm-spelling-error')
  61. cy.log('remove word from dictionary')
  62. cy.get('button').contains('Menu').click()
  63. cy.get('button#dictionary-settings').contains('Edit').click()
  64. cy.get('[id="dictionary-modal"]').within(() => {
  65. cy.findByText(word)
  66. .parent()
  67. .within(() => cy.get('button').click())
  68. // the modal has 2 close buttons, this ensures the one with the visible label is
  69. // clicked, otherwise it would need `force: true`
  70. cy.get('.btn').contains('Close').click()
  71. })
  72. cy.log('close left panel')
  73. cy.get('[id="left-menu"]').type('{esc}')
  74. cy.log('rewrite word to force spelling error')
  75. cy.get('.cm-line').type('{selectAll}{del}' + word + '{enter}')
  76. cy.get('.ol-cm-spelling-error').should('contain.text', word)
  77. })
  78. })
  79. describe('editor', () => {
  80. it('renders jpg', () => {
  81. cy.findByTestId('file-tree').findByText('frog.jpg').click()
  82. cy.get('[alt="frog.jpg"]')
  83. .should('be.visible')
  84. .and('have.prop', 'naturalWidth')
  85. .should('be.greaterThan', 0)
  86. })
  87. it('symbol palette', () => {
  88. createNewFile()
  89. cy.get('button[aria-label="Toggle Symbol Palette"]').click({
  90. force: true,
  91. })
  92. cy.get('button').contains('𝜉').click()
  93. cy.findByRole('textbox', { name: /Source Editor editing/i }).should(
  94. 'contain.text',
  95. '\\xi'
  96. )
  97. cy.log('recompile to force flush and avoid "unsaved changes" prompt')
  98. recompile()
  99. })
  100. })
  101. describe('add new file to project', () => {
  102. beforeEach(() => {
  103. cy.get('button').contains('New file').click({ force: true })
  104. })
  105. testNewFileUpload()
  106. it('should not display import from URL', () => {
  107. cy.findByText('From external URL').should('not.exist')
  108. })
  109. })
  110. describe('left menu', () => {
  111. beforeEach(() => {
  112. cy.get('button').contains('Menu').click()
  113. })
  114. it('can download project sources', () => {
  115. cy.get('a').contains('Source').click()
  116. const zipName = projectName.replaceAll('-', '_')
  117. cy.task('readFileInZip', {
  118. pathToZip: `cypress/downloads/${zipName}.zip`,
  119. fileToRead: 'main.tex',
  120. }).should('contain', 'Your introduction goes here')
  121. })
  122. it('can download project PDF', () => {
  123. cy.log('ensure project is compiled')
  124. cy.get('.pdf-viewer').should('contain.text', 'Your Paper')
  125. cy.get('.nav-downloads').within(() => {
  126. cy.findByText('PDF').click()
  127. const pdfName = projectName.replaceAll('-', '_')
  128. cy.task('readPdf', `cypress/downloads/${pdfName}.pdf`).should(
  129. 'contain',
  130. 'Your introduction goes here'
  131. )
  132. })
  133. })
  134. it('word count', () => {
  135. cy.log('ensure project is compiled')
  136. cy.get('.pdf-viewer').should('contain.text', 'Your Paper')
  137. cy.findByText('Word Count').click()
  138. cy.get('#word-count-modal').within(() => {
  139. cy.findByText('Total Words:')
  140. cy.findByText('607')
  141. cy.findByText('Headers:')
  142. cy.findByText('14')
  143. cy.findByText('Math Inline:')
  144. cy.findByText('6')
  145. cy.findByText('Math Display:')
  146. cy.findByText('1')
  147. })
  148. })
  149. })
  150. describe('layout selector', () => {
  151. it('show editor only and switch between editor and pdf', () => {
  152. cy.get('.pdf-viewer').should('be.visible')
  153. cy.get('.cm-editor').should('be.visible')
  154. cy.findByText('Layout').click()
  155. cy.findByText('Editor only').click()
  156. cy.get('.pdf-viewer').should('not.be.visible')
  157. cy.get('.cm-editor').should('be.visible')
  158. cy.findByText('Switch to PDF').click()
  159. cy.get('.pdf-viewer').should('be.visible')
  160. cy.get('.cm-editor').should('not.be.visible')
  161. cy.findByText('Switch to editor').click()
  162. cy.get('.pdf-viewer').should('not.be.visible')
  163. cy.get('.cm-editor').should('be.visible')
  164. })
  165. it('show PDF only and go back to Editor & PDF', () => {
  166. cy.get('.pdf-viewer').should('be.visible')
  167. cy.get('.cm-editor').should('be.visible')
  168. cy.findByText('Layout').click()
  169. cy.findByText('PDF only').click()
  170. cy.get('.pdf-viewer').should('be.visible')
  171. cy.get('.cm-editor').should('not.be.visible')
  172. cy.findByText('Layout').click()
  173. cy.findByText('Editor & PDF').click()
  174. cy.get('.pdf-viewer').should('be.visible')
  175. cy.get('.cm-editor').should('be.visible')
  176. })
  177. it('PDF in a separate tab (tests editor only)', () => {
  178. cy.get('.pdf-viewer').should('be.visible')
  179. cy.get('.cm-editor').should('be.visible')
  180. cy.findByText('Layout').click()
  181. cy.findByText('PDF in separate tab').click()
  182. cy.get('.pdf-viewer').should('not.exist')
  183. cy.get('.cm-editor').should('be.visible')
  184. })
  185. })
  186. })
  187. function createRandomLetterString() {
  188. const chars = 'abcdefghijklmnopqrstuvwxyz'
  189. let result = ''
  190. for (let i = 0; i < 12; i++) {
  191. result += chars.charAt(Math.floor(Math.random() * chars.length))
  192. }
  193. return result
  194. }