project.ts 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251
  1. import { login } from './login'
  2. import { openEmail } from './email'
  3. import { v4 as uuid } from 'uuid'
  4. export function createProject(
  5. name: string,
  6. {
  7. type = 'Blank Project',
  8. newProjectButtonMatcher = /new project/i,
  9. open = true,
  10. }: {
  11. type?: 'Blank Project' | 'Example Project'
  12. newProjectButtonMatcher?: RegExp
  13. open?: boolean
  14. } = {}
  15. ): Cypress.Chainable<string> {
  16. cy.url().then(url => {
  17. if (!url.endsWith('/project')) {
  18. cy.visit('/project')
  19. }
  20. })
  21. const interceptId = uuid()
  22. let projectId = ''
  23. if (!open) {
  24. cy.then(() => {
  25. // Register intercept just before creating the project, otherwise we might
  26. // intercept a request from a prior createProject invocation.
  27. cy.intercept(
  28. { method: 'GET', url: /\/project\/[a-fA-F0-9]{24}$/, times: 1 },
  29. req => {
  30. projectId = req.url.split('/').pop()!
  31. // Redirect back to the project dashboard, effectively reload the page.
  32. req.redirect('/project')
  33. }
  34. ).as(interceptId)
  35. })
  36. }
  37. cy.findAllByRole('button').contains(newProjectButtonMatcher).click()
  38. // FIXME: This should only look in the left menu
  39. cy.findAllByText(type).first().click()
  40. cy.findByRole('dialog').within(() => {
  41. cy.get('input').type(name)
  42. cy.findByText('Create').click()
  43. })
  44. if (open) {
  45. cy.url().should('match', /\/project\/[a-fA-F0-9]{24}/)
  46. waitForMainDocToLoad()
  47. return cy
  48. .url()
  49. .should('match', /\/project\/[a-fA-F0-9]{24}/)
  50. .then(url => url.split('/').pop())
  51. } else {
  52. const alias = `@${interceptId}` // IDEs do not like computed values in cy.wait().
  53. cy.wait(alias)
  54. return cy.then(() => projectId)
  55. }
  56. }
  57. export function openProjectByName(projectName: string) {
  58. cy.visit('/project')
  59. cy.findByText(projectName).click()
  60. waitForMainDocToLoad()
  61. }
  62. export function openProjectById(projectId: string) {
  63. cy.visit(`/project/${projectId}`)
  64. waitForMainDocToLoad()
  65. }
  66. export function openProjectViaLinkSharingAsAnon(url: string) {
  67. cy.visit(url)
  68. waitForMainDocToLoad()
  69. }
  70. export function openProjectViaLinkSharingAsUser(
  71. url: string,
  72. projectName: string,
  73. email: string
  74. ) {
  75. cy.visit(url)
  76. cy.findByText(projectName) // wait for lazy loading
  77. cy.contains(`as ${email}`)
  78. cy.findByText('OK, join project').click()
  79. waitForMainDocToLoad()
  80. }
  81. export function openProjectViaInviteNotification(projectName: string) {
  82. cy.visit('/project')
  83. cy.findByText(projectName)
  84. .parent()
  85. .parent()
  86. .within(() => {
  87. cy.findByText('Join Project').click()
  88. })
  89. cy.findByText('Open Project').click()
  90. cy.url().should('match', /\/project\/[a-fA-F0-9]{24}/)
  91. waitForMainDocToLoad()
  92. }
  93. function shareProjectByEmail(
  94. projectName: string,
  95. email: string,
  96. level: 'Viewer' | 'Editor'
  97. ) {
  98. openProjectByName(projectName)
  99. cy.findByText('Share').click()
  100. cy.findByRole('dialog').within(() => {
  101. cy.findByLabelText('Add people', { selector: 'input' }).type(`${email},`)
  102. cy.findByLabelText('Add people', { selector: 'input' })
  103. .parents('form')
  104. .within(() => {
  105. cy.findByTestId('add-collaborator-select')
  106. .click()
  107. .then(() => {
  108. cy.findByText(level).click()
  109. })
  110. })
  111. cy.findByText('Invite').click({ force: true })
  112. cy.findByText('Invite not yet accepted.')
  113. })
  114. }
  115. export function shareProjectByEmailAndAcceptInviteViaDash(
  116. projectName: string,
  117. email: string,
  118. level: 'Viewer' | 'Editor'
  119. ) {
  120. shareProjectByEmail(projectName, email, level)
  121. login(email)
  122. openProjectViaInviteNotification(projectName)
  123. }
  124. export function shareProjectByEmailAndAcceptInviteViaEmail(
  125. projectName: string,
  126. email: string,
  127. level: 'Viewer' | 'Editor'
  128. ) {
  129. shareProjectByEmail(projectName, email, level)
  130. login(email)
  131. openEmail(projectName, frame => {
  132. frame.contains('View project').then(a => {
  133. cy.log(
  134. 'bypass target=_blank and navigate current browser tab/cypress-iframe to project invite'
  135. )
  136. cy.visit(a.attr('href')!)
  137. })
  138. })
  139. cy.url().should('match', /\/project\/[a-f0-9]+\/invite\/token\/[a-f0-9]+/)
  140. cy.findByText(/user would like you to join/)
  141. cy.contains(new RegExp(`You are accepting this invite as ${email}`))
  142. cy.findByText('Join Project').click()
  143. waitForMainDocToLoad()
  144. }
  145. export function enableLinkSharing() {
  146. let linkSharingReadOnly: string
  147. let linkSharingReadAndWrite: string
  148. const origin = new URL(Cypress.config().baseUrl!).origin
  149. waitForMainDocToLoad()
  150. cy.findByText('Share').click()
  151. cy.findByText('Turn on link sharing').click()
  152. cy.findByText('Anyone with this link can view this project')
  153. .next()
  154. .should('contain.text', origin + '/read')
  155. .then(el => {
  156. linkSharingReadOnly = el.text()
  157. })
  158. cy.findByText('Anyone with this link can edit this project')
  159. .next()
  160. .should('contain.text', origin + '/')
  161. .then(el => {
  162. linkSharingReadAndWrite = el.text()
  163. })
  164. return cy.then(() => {
  165. return { linkSharingReadOnly, linkSharingReadAndWrite }
  166. })
  167. }
  168. export function waitForMainDocToLoad() {
  169. cy.log('Wait for main doc to load; it will steal the focus after loading')
  170. cy.get('.cm-content').should('contain.text', 'Introduction')
  171. }
  172. export function openFile(fileName: string, waitFor: string) {
  173. // force: The file-tree pane is too narrow to display the full name.
  174. cy.findByTestId('file-tree').findByText(fileName).click({ force: true })
  175. // wait until we've switched to the selected file
  176. cy.findByText('Loading…').should('not.exist')
  177. cy.findByText(waitFor)
  178. }
  179. export function createNewFile() {
  180. const fileName = `${uuid()}.tex`
  181. cy.log('create new project file')
  182. cy.get('button').contains('New file').click({ force: true })
  183. cy.findByRole('dialog').within(() => {
  184. cy.get('input').clear()
  185. cy.get('input').type(fileName)
  186. cy.findByText('Create').click()
  187. })
  188. // force: The file-tree pane is too narrow to display the full name.
  189. cy.findByTestId('file-tree').findByText(fileName).click({ force: true })
  190. // wait until we've switched to the newly created empty file
  191. cy.findByText('Loading…').should('not.exist')
  192. cy.get('.cm-line').should('have.length', 1)
  193. return fileName
  194. }
  195. export function toggleTrackChanges(state: boolean) {
  196. cy.findByText('Review').click()
  197. cy.get('.track-changes-menu-button').then(el => {
  198. // when the menu is expanded renders the `expand_more` icon,
  199. // and the `chevron_right` icon when it's collapsed
  200. if (!el.text().includes('expand_more')) {
  201. el.click()
  202. }
  203. })
  204. cy.findByText('Everyone')
  205. .parent()
  206. .within(() => {
  207. cy.get('.form-check-input').then(el => {
  208. if (el.prop('checked') === state) return
  209. const id = uuid()
  210. const alias = `@${id}`
  211. cy.intercept({
  212. method: 'POST',
  213. url: '**/track_changes',
  214. times: 1,
  215. }).as(id)
  216. if (state) {
  217. cy.get('.form-check-input').check()
  218. } else {
  219. cy.get('.form-check-input').uncheck()
  220. }
  221. cy.wait(alias)
  222. })
  223. })
  224. cy.contains('.toolbar-item', 'Review').click()
  225. }