| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402 |
- import { login } from './login'
- import { openEmail } from './email'
- import { v4 as uuid } from 'uuid'
- import { prepareWaitForNextCompileSlot } from './compile'
- export const NEW_PROJECT_BUTTON_MATCHER = /new project/i
- const NEW_EDITOR_QUERY_PARAMS = ''
- const OLD_EDITOR_QUERY_PARAMS = '?old-editor-override=true'
- export function redirectEditorUrlWithQueryParams(newEditor: boolean) {
- const queryString = newEditor
- ? NEW_EDITOR_QUERY_PARAMS
- : OLD_EDITOR_QUERY_PARAMS
- cy.intercept(
- { method: 'GET', url: /\/project\/[a-fA-F0-9]{24}$/, times: 1 },
- req => {
- // Intercept redirect and add the query param to use the new editor
- req.redirect(`${req.url}${queryString}`)
- }
- )
- }
- export function createProject(
- name: string,
- {
- type = 'Blank project',
- newProjectButtonMatcher = NEW_PROJECT_BUTTON_MATCHER,
- open = true,
- newEditor = false,
- }: {
- type?: 'Blank project' | 'Example project'
- newProjectButtonMatcher?: RegExp
- open?: boolean
- newEditor?: boolean
- } = {}
- ): Cypress.Chainable<string> {
- cy.url().then(url => {
- if (!url.endsWith('/project')) {
- cy.visit('/project')
- }
- })
- const interceptId = uuid()
- let projectId = ''
- if (!open) {
- cy.then(() => {
- // Register intercept just before creating the project, otherwise we might
- // intercept a request from a prior createProject invocation.
- cy.intercept(
- { method: 'GET', url: /\/project\/[a-fA-F0-9]{24}$/, times: 1 },
- req => {
- projectId = req.url.split('/').pop()!.split('?')[0]
- // Redirect back to the project dashboard, effectively reload the page.
- req.redirect('/project')
- }
- ).as(interceptId)
- })
- } else {
- redirectEditorUrlWithQueryParams(newEditor)
- }
- cy.findAllByRole('button').contains(newProjectButtonMatcher).click()
- // FIXME: This should only look in the left menu
- // The upgrading tests create projects in older versions of Server Pro which used different casing of the project type. Use case-insensitive match.
- cy.findAllByText(type, { exact: false }).first().click()
- cy.findByRole('dialog').within(() => {
- cy.get('input').type(name)
- cy.findByRole('button', { name: 'Create' }).click()
- })
- if (open) {
- cy.url().should('match', /\/project\/[a-fA-F0-9]{24}/)
- waitForMainDocToLoad()
- return cy
- .url()
- .should('match', /\/project\/[a-fA-F0-9]{24}/)
- .then(url => url.split('/').pop()?.split('?')[0])
- } else {
- const alias = `@${interceptId}` // IDEs do not like computed values in cy.wait().
- cy.wait(alias)
- return cy.then(() => projectId)
- }
- }
- // TODO ide-redesign-cleanup: Remove this and just use createProject directly
- export function createProjectAndOpenInNewEditor(
- projectName: string,
- options: {
- type?: 'Blank project' | 'Example project'
- newProjectButtonMatcher?: RegExp
- } = {}
- ) {
- return createProject(projectName, { ...options, open: false }).then(
- projectId => {
- // Open the new project in the new editor
- openProjectById(projectId, true)
- return cy.then(() => projectId)
- }
- )
- }
- export function openProjectByName(projectName: string, newEditor = false) {
- cy.visit('/project')
- redirectEditorUrlWithQueryParams(newEditor)
- cy.findByText(projectName).click()
- waitForMainDocToLoad()
- if (newEditor) {
- // Close the beta intro modal if it appears
- // TODO ide-redesign-cleanup: Remove this when the intro modal is removed
- cy.get('body').type('{esc}')
- }
- }
- export function openProjectById(projectId: string, newEditor = false) {
- const url = newEditor
- ? `/project/${projectId}${NEW_EDITOR_QUERY_PARAMS}`
- : `/project/${projectId}${OLD_EDITOR_QUERY_PARAMS}`
- cy.visit(url)
- waitForMainDocToLoad()
- if (newEditor) {
- // Close the beta intro modal if it appears
- // TODO ide-redesign-cleanup: Remove this when the intro modal is removed
- cy.get('body').type('{esc}')
- }
- }
- export function openProjectViaLinkSharingAsAnon(
- url: string,
- newEditor = false
- ) {
- redirectEditorUrlWithQueryParams(newEditor)
- cy.visit(url)
- waitForMainDocToLoad()
- if (newEditor) {
- // Close the beta intro modal if it appears
- // TODO ide-redesign-cleanup: Remove this when the intro modal is removed
- cy.get('body').type('{esc}')
- }
- }
- export function openProjectViaLinkSharingAsUser(
- url: string,
- projectName: string,
- email: string,
- newEditor: boolean = false
- ) {
- cy.visit(url)
- cy.findByText(projectName) // wait for lazy loading
- cy.contains(`as ${email}`)
- redirectEditorUrlWithQueryParams(newEditor)
- cy.findByText('OK, join project').click()
- waitForMainDocToLoad()
- if (newEditor) {
- // Close the beta intro modal if it appears
- // TODO ide-redesign-cleanup: Remove this when the intro modal is removed
- cy.get('body').type('{esc}')
- }
- }
- export function openProjectViaInviteNotification(
- projectName: string,
- newEditor: boolean = false
- ) {
- cy.visit('/project')
- cy.findByText(projectName)
- .parent()
- .parent()
- .within(() => {
- cy.findByText('Join Project').click()
- })
- cy.intercept(
- // NOTE: Struggling to work out why but this only seems to work with times: 2.
- // This is temporary code so leaving it for now.
- { method: 'GET', url: /\/project\/[a-fA-F0-9]{24}$/, times: 2 },
- req => {
- const queryString = newEditor
- ? NEW_EDITOR_QUERY_PARAMS
- : OLD_EDITOR_QUERY_PARAMS
- // Intercept redirect and add the query param to use the new editor
- req.redirect(`${req.url}${queryString}`)
- }
- )
- const { waitForCompile } = prepareWaitForNextCompileSlot()
- waitForCompile(() => {
- cy.findByText('Open Project').click()
- cy.url().should('match', /\/project\/[a-fA-F0-9]{24}/)
- waitForMainDocToLoad()
- if (newEditor) {
- // Close the beta intro modal if it appears
- // TODO ide-redesign-cleanup: Remove this when the intro modal is removed
- cy.get('body').type('{esc}')
- }
- })
- }
- function shareProjectByEmail(
- projectName: string,
- email: string,
- level: 'Viewer' | 'Editor',
- newEditor: boolean = false
- ) {
- openProjectByName(projectName, newEditor)
- cy.findByRole('button', { name: 'Share' }).click()
- cy.findByRole('dialog').within(() => {
- cy.findByLabelText('Add email address', { selector: 'input' }).type(
- `${email},`
- )
- cy.findByLabelText('Add email address', { selector: 'input' })
- .parents('form')
- .within(() => {
- cy.findByTestId('add-collaborator-select').as('select').click()
- cy.get('@select').then(() => {
- cy.findByRole('option', { name: level }).click()
- })
- })
- cy.findByRole('button', { name: 'Invite' }).click()
- cy.findByText('Invite not yet accepted.')
- })
- }
- export function shareProjectByEmailAndAcceptInviteViaDash(
- projectName: string,
- email: string,
- level: 'Viewer' | 'Editor',
- newEditor: boolean = false
- ) {
- shareProjectByEmail(projectName, email, level, newEditor)
- login(email)
- openProjectViaInviteNotification(projectName, newEditor)
- }
- export function getSpamSafeProjectName() {
- while (true) {
- // Move from hex/16 to base64/64 possible characters per char in string
- const name = Buffer.from(uuid().replaceAll('-', ''), 'hex')
- .toString('base64')
- .replaceAll('/', 'x')
- .replaceAll('+', 'y')
- .slice(0, 10)
- const nDigits = (name.match(/\d/g) || []).length
- if (nDigits < 6) return name
- }
- }
- export function shareProjectByEmailAndAcceptInviteViaEmail(
- projectName: string,
- email: string,
- level: 'Viewer' | 'Editor',
- newEditor: boolean = false
- ) {
- shareProjectByEmail(projectName, email, level, newEditor)
- login(email)
- openEmail(projectName, frame => {
- frame.contains('View project').then(a => {
- cy.log(
- 'bypass target=_blank and navigate current browser tab/cypress-iframe to project invite'
- )
- cy.visit(a.attr('href')!)
- })
- })
- cy.url().should('match', /\/project\/[a-f0-9]+\/invite\/token\/[a-f0-9]+/)
- cy.findByText(/user would like you to join/)
- cy.contains(new RegExp(`You are accepting this invite as ${email}`))
- redirectEditorUrlWithQueryParams(newEditor)
- cy.findByText('Join Project').click()
- waitForMainDocToLoad()
- if (newEditor) {
- // Close the beta intro modal if it appears
- // TODO ide-redesign-cleanup: Remove this when the intro modal is removed
- cy.get('body').type('{esc}')
- }
- }
- export function enableLinkSharing() {
- let linkSharingReadOnly: string
- let linkSharingReadAndWrite: string
- const origin = new URL(Cypress.config().baseUrl!).origin
- waitForMainDocToLoad()
- cy.findByText('Share').click()
- cy.findByText('Turn on link sharing').click()
- cy.findByText('Anyone with this link can view this project')
- .next()
- .should('contain.text', origin + '/read')
- .then(el => {
- linkSharingReadOnly = el.text()
- })
- cy.findByText('Anyone with this link can edit this project')
- .next()
- .should('contain.text', origin + '/')
- .then(el => {
- linkSharingReadAndWrite = el.text()
- })
- return cy.then(() => {
- return { linkSharingReadOnly, linkSharingReadAndWrite }
- })
- }
- export function waitForMainDocToLoad() {
- cy.log('Wait for main doc to load; it will steal the focus after loading')
- cy.get('.cm-content').should('contain.text', 'Introduction')
- }
- export function openFile(fileName: string, waitFor: string) {
- // force: The file-tree pane is too narrow to display the full name.
- cy.findByRole('navigation', { name: 'Project files and outline' })
- .findByRole('treeitem', { name: fileName })
- .click({ force: true })
- // wait until we've switched to the selected file
- cy.findByRole('status').should('not.exist')
- cy.findByText(waitFor)
- }
- export function createNewFile() {
- const fileName = `${uuid()}.tex`
- cy.log('create new project file')
- cy.get('button').contains('New file').click({ force: true })
- cy.findByRole('dialog').within(() => {
- cy.get('input').clear()
- cy.get('input').type(fileName)
- cy.findByText('Create').click()
- })
- // force: The file-tree pane is too narrow to display the full name.
- cy.findByTestId('file-tree')
- .findByRole('treeitem', { name: fileName })
- .click({ force: true })
- // wait until we've switched to the newly created empty file
- cy.findByRole('textbox', { name: 'Source Editor editing' }).within(() => {
- cy.findByRole('status').should('not.exist')
- cy.get('.cm-line').should('have.length', 1)
- })
- cy.get('.cm-line').should('have.length', 1)
- return fileName
- }
- export function expectFileExists(
- name: string,
- binary: boolean,
- content: string
- ) {
- cy.findByRole('treeitem', { name }).click()
- if (binary) {
- cy.findByText(content).should('not.have.class', 'cm-line')
- } else {
- cy.findByText(content).should('have.class', 'cm-line')
- }
- }
- export function prepareFileUploadTest(binary = false) {
- const name = `${uuid()}.txt`
- const content = `Test File Content ${name}${binary ? ' \x00' : ''}`
- cy.get('button').contains('Upload').click({ force: true })
- cy.get('input[type=file]')
- .first()
- .selectFile(
- {
- contents: Cypress.Buffer.from(content),
- fileName: name,
- lastModified: Date.now(),
- },
- { force: true }
- )
- // wait for the upload to finish
- cy.findByRole('treeitem', { name })
- return () => expectFileExists(name, binary, content)
- }
- export function testNewFileUpload() {
- it('can upload text file', function () {
- const check = prepareFileUploadTest(false)
- check()
- })
- it('can upload binary file', function () {
- const check = prepareFileUploadTest(true)
- check()
- })
- }
|