From 9e0de43638c2b98235ad4665d14ff7730459fdc8 Mon Sep 17 00:00:00 2001 From: Mathias Jakobsen Date: Mon, 26 Jan 2026 11:27:55 +0000 Subject: [PATCH] Merge pull request #30910 from overleaf/mj-tear-down-old-e2e [e2e] Remove old editor E2E tests GitOrigin-RevId: 960c5cd5f17c2a5bae225ecb53fa0eed76938939 --- .../test/create-and-compile-project.spec.ts | 150 ----- server-ce/test/editor.spec.ts | 338 ------------ server-ce/test/git-bridge.spec.ts | 492 ----------------- server-ce/test/graceful-shutdown.spec.ts | 111 ---- server-ce/test/history.spec.ts | 164 ------ server-ce/test/project-sharing.spec.ts | 518 ------------------ server-ce/test/sandboxed-compiles.spec.ts | 352 ------------ server-ce/test/templates.spec.ts | 280 ---------- .../ide-redesign/utils/new-editor-utils.ts | 7 +- 9 files changed, 1 insertion(+), 2411 deletions(-) delete mode 100644 server-ce/test/create-and-compile-project.spec.ts delete mode 100644 server-ce/test/editor.spec.ts delete mode 100644 server-ce/test/git-bridge.spec.ts delete mode 100644 server-ce/test/graceful-shutdown.spec.ts delete mode 100644 server-ce/test/history.spec.ts delete mode 100644 server-ce/test/project-sharing.spec.ts delete mode 100644 server-ce/test/sandboxed-compiles.spec.ts delete mode 100644 server-ce/test/templates.spec.ts diff --git a/server-ce/test/create-and-compile-project.spec.ts b/server-ce/test/create-and-compile-project.spec.ts deleted file mode 100644 index f7c0712bab..0000000000 --- a/server-ce/test/create-and-compile-project.spec.ts +++ /dev/null @@ -1,150 +0,0 @@ -import { ensureUserExists, login } from './helpers/login' -import { - createProject, - openProjectViaInviteNotification, -} from './helpers/project' -import { isExcludedBySharding, startWith } from './helpers/config' -import { prepareWaitForNextCompileSlot } from './helpers/compile' - -const USER = 'user@example.com' -const COLLABORATOR = 'collaborator@example.com' - -describe('Project creation and compilation', function () { - if (isExcludedBySharding('CE_DEFAULT')) return - startWith({}) - ensureUserExists({ email: USER }) - ensureUserExists({ email: COLLABORATOR }) - - it('users can create project and compile it', function () { - login(USER) - const { recompile, waitForCompile } = prepareWaitForNextCompileSlot() - waitForCompile(() => { - createProject('test-project') - }) - cy.findByRole('textbox', { name: 'Source Editor editing' }).within(() => { - cy.findByText('\\maketitle').parent().click() - cy.findByText('\\maketitle').parent().type('\n\\section{{}Test Section}') - }) - recompile() - cy.findByRole('region', { name: 'PDF preview and logs' }).within(() => { - cy.findByLabelText(/Page.*1/i).should('be.visible') - cy.findByText('Test Section').should('be.visible') - }) - }) - - it('create and edit markdown file', function () { - const fileName = `test-${Date.now()}.md` - const markdownContent = '# Markdown title' - login(USER) - createProject('test-project') - - cy.findByRole('navigation', { name: 'Project files and outline' }) - .findByRole('button', { name: 'New file' }) - .click() - cy.findByRole('dialog').within(() => { - cy.findByLabelText('File Name').as('filename').clear() - cy.get('@filename').type(fileName) - cy.findByRole('button', { name: 'Create' }).click() - }) - cy.findByRole('button', { name: fileName }).click() - // wait until we've switched to the newly created empty file - cy.findByRole('textbox', { name: 'Source Editor editing' }).should( - 'have.length', - 1 - ) - cy.findByRole('textbox', { name: 'Source Editor editing' }).type( - markdownContent - ) - cy.findByRole('button', { name: 'main.tex' }).click() - cy.findByRole('textbox', { name: 'Source Editor editing' }).should( - 'contain.text', - '\\maketitle' - ) - cy.findByRole('button', { name: fileName }).click() - cy.findByRole('textbox', { name: 'Source Editor editing' }).should( - 'contain.text', - markdownContent - ) - }) - - it('can link and display linked image from other project', function () { - const sourceProjectName = `test-project-${Date.now()}` - const targetProjectName = `${sourceProjectName}-target` - login(USER) - - createProject(sourceProjectName, { - type: 'Example project', - open: false, - }).as('sourceProjectId') - createProject(targetProjectName) - - // link the image from `projectName` into this project - cy.findByRole('button', { name: 'New file' }).click() - cy.findByRole('dialog').within(() => { - cy.findByRole('button', { name: 'From another project' }).click() - cy.findByLabelText('Select a Project').select(sourceProjectName) - cy.findByLabelText('Select a File').select('frog.jpg') - cy.findByRole('button', { name: 'Create' }).click() - }) - cy.findByRole('navigation', { name: 'Project files and outline' }) - .findByRole('treeitem', { name: 'frog.jpg' }) - .click() - cy.findByRole('link', { name: 'Another project' }) - .should('have.attr', 'href') - .then(href => { - cy.get('@sourceProjectId').then(sourceProjectId => { - expect(href).to.equal(`/project/${sourceProjectId}`) - }) - }) - }) - - it('can refresh linked files as collaborator', function () { - const sourceProjectName = `test-project-${Date.now()}` - const targetProjectName = `${sourceProjectName}-target` - login(USER) - createProject(sourceProjectName, { - type: 'Example project', - open: false, - }).as('sourceProjectId') - createProject(targetProjectName).as('targetProjectId') - - // link the image from `projectName` into this project - cy.findByRole('navigation', { name: 'Project files and outline' }) - .findByRole('button', { name: 'New file' }) - .click() - cy.findByRole('dialog').within(() => { - cy.findByRole('button', { name: 'From another project' }).click() - cy.findByLabelText('Select a Project').select(sourceProjectName) - cy.findByLabelText('Select a File').select('frog.jpg') - cy.findByRole('button', { name: 'Create' }).click() - }) - - cy.findByRole('navigation', { name: 'Project actions' }).within(() => { - cy.findByRole('button', { name: 'Share' }).click() - }) - cy.findByRole('dialog').within(() => { - cy.findByRole('combobox', { name: 'Add email address' }).type( - COLLABORATOR + ',' - ) - cy.findByRole('button', { name: 'Invite' }).click() - cy.findByText('Invite not yet accepted.') - }) - - login(COLLABORATOR) - openProjectViaInviteNotification(targetProjectName) - cy.get('@targetProjectId').then(targetProjectId => { - cy.url().should('include', targetProjectId) - }) - - cy.findByRole('navigation', { name: 'Project files and outline' }) - .findByRole('treeitem', { name: 'frog.jpg' }) - .click() - cy.findByRole('link', { name: 'Another project' }) - .should('have.attr', 'href') - .then(href => { - cy.get('@sourceProjectId').then(sourceProjectId => { - expect(href).to.equal(`/project/${sourceProjectId}`) - }) - }) - }) -}) diff --git a/server-ce/test/editor.spec.ts b/server-ce/test/editor.spec.ts deleted file mode 100644 index 2a7138424e..0000000000 --- a/server-ce/test/editor.spec.ts +++ /dev/null @@ -1,338 +0,0 @@ -import { - createNewFile, - createProject, - openProjectById, - testNewFileUpload, -} from './helpers/project' -import { isExcludedBySharding, startWith } from './helpers/config' -import { ensureUserExists, login } from './helpers/login' -import { v4 as uuid } from 'uuid' -import { beforeWithReRunOnTestRetry } from './helpers/beforeWithReRunOnTestRetry' -import { prepareWaitForNextCompileSlot } from './helpers/compile' - -const USER = 'user@example.com' -const COLLABORATOR = 'collaborator@example.com' - -describe('editor', function () { - if (isExcludedBySharding('PRO_DEFAULT_1')) return - startWith({ pro: true }) - ensureUserExists({ email: USER }) - ensureUserExists({ email: COLLABORATOR }) - - let projectName: string - let projectId: string - let recompile: () => void - let waitForCompile: (fn: () => void) => void - beforeWithReRunOnTestRetry(() => { - projectName = `project-${uuid()}` - login(USER) - createProject(projectName, { type: 'Example project', open: false }).then( - id => (projectId = id) - ) - ;({ recompile, waitForCompile } = prepareWaitForNextCompileSlot()) - }) - - beforeEach(function () { - login(USER) - waitForCompile(() => { - openProjectById(projectId) - }) - }) - - describe('spelling', function () { - function changeSpellCheckLanguageTo(lng: string) { - cy.log(`change project language to '${lng}'`) - cy.findByRole('navigation', { - name: 'Project actions', - }) - .findByRole('button', { name: 'Menu' }) - .click() - - cy.findByRole('dialog').within(() => { - cy.findByLabelText('Spell check').select(lng) - }) - cy.get('body').type('{esc}') - } - - afterEach(function () { - changeSpellCheckLanguageTo('Off') - }) - - it('word dictionary and spelling', function () { - changeSpellCheckLanguageTo('English (American)') - createNewFile() - const word = createRandomLetterString() - - cy.log('edit project file') - cy.get('.cm-line').type(word) - cy.findByText(word).should('have.class', 'ol-cm-spelling-error') - - changeSpellCheckLanguageTo('Off') - cy.findByText(word).should('not.have.class', 'ol-cm-spelling-error') - - changeSpellCheckLanguageTo('Spanish') - cy.findByText(word).should('have.class', 'ol-cm-spelling-error') - - cy.log('add word to dictionary') - cy.findByText(word).rightclick() - cy.findByRole('menuitem', { name: 'Add to dictionary' }).click() - cy.findByText(word).should('not.have.class', 'ol-cm-spelling-error') - - cy.log('remove word from dictionary') - cy.findByRole('navigation', { - name: 'Project actions', - }) - .findByRole('button', { name: 'Menu' }) - .click() - cy.findByRole('dialog').within(() => { - cy.findByLabelText('Dictionary').click() - }) - cy.findByTestId('dictionary-modal').within(() => { - cy.findByText(word) - .parent() - .within(() => - cy.findByRole('button', { name: 'Remove from dictionary' }).click() - ) - - // the modal has 2 close buttons, this ensures the one with the visible label is - // clicked, otherwise it would need `force: true` - cy.contains('button', /close/i).click() - }) - - cy.log('close left panel') - cy.findByTestId('left-menu').type('{esc}') - - cy.log('rewrite word to force spelling error') - cy.get('.cm-line').type('{selectAll}{del}' + word + '{enter}') - - cy.get('.ol-cm-spelling-error').should('contain.text', word) - }) - }) - - describe('editor', function () { - it('renders jpg', function () { - cy.findByRole('navigation', { - name: 'Project files and outline', - }) - .findByRole('treeitem', { name: 'frog.jpg' }) - .click() - cy.get('[alt="frog.jpg"]') - .should('be.visible') - .and('have.prop', 'naturalWidth') - .should('be.greaterThan', 0) - }) - - it('symbol palette', function () { - createNewFile() - - cy.get('button[aria-label="Insert symbol"]').click({ - force: true, - }) - cy.get('button').contains('𝜉').click() - cy.findByRole('textbox', { name: 'Source Editor editing' }).should( - 'contain.text', - '\\xi' - ) - - cy.log('recompile to force flush and avoid "unsaved changes" prompt') - recompile() - }) - }) - - describe('add new file to project', function () { - beforeEach(function () { - cy.findByRole('button', { name: 'New file' }).click() - }) - - testNewFileUpload() - - it('should not display import from URL', function () { - cy.findByRole('button', { name: 'From external URL' }).should('not.exist') - }) - }) - - describe('left menu', function () { - beforeEach(function () { - cy.findByRole('navigation', { - name: 'Project actions', - }) - .findByRole('button', { name: 'Menu' }) - .click() - }) - - it('can download project sources', function () { - cy.findByRole('link', { name: 'Source' }).click() - const zipName = projectName.replaceAll('-', '_') - cy.task('readFileInZip', { - pathToZip: `cypress/downloads/${zipName}.zip`, - fileToRead: 'main.tex', - }).should('contain', 'Your introduction goes here') - }) - - it('can download project PDF', function () { - cy.log('ensure project is compiled') - cy.findByRole('region', { name: 'PDF preview and logs' }).should( - 'contain.text', - 'Your Paper' - ) - cy.findByRole('dialog').within(() => { - cy.findByRole('link', { name: 'PDF' }).click() - const pdfName = projectName.replaceAll('-', '_') - cy.task('readPdf', `cypress/downloads/${pdfName}.pdf`).should( - 'contain', - 'Your introduction goes here' - ) - }) - }) - - it('word count', function () { - cy.log('ensure project is compiled') - cy.findByRole('region', { name: 'PDF preview and logs' }).should( - 'contain.text', - 'Your Paper' - ) - cy.findByRole('button', { name: 'Word Count' }).click() - - cy.findByTestId('word-count-modal').within(() => { - cy.findByText('Total Words:') - cy.findByText('607') - cy.findByText('Headers:') - cy.findByText('14') - cy.findByText('Math Inline:') - cy.findByText('6') - cy.findByText('Math Display:') - cy.findByText('1') - }) - }) - }) - - describe('cite key search', function () { - it('can insert citation from cite key', function () { - createNewFile() - cy.get('.cm-line').type('\\cite{{}gre') - cy.findByRole('listbox').within(() => { - cy.findByRole('option').should('contain.text', 'greenwade93').click() - }) - cy.get('.cm-line').should('have.text', '\\cite{greenwade93}') - }) - - it('updates citation search when bib file is changed', function () { - createNewFile() - cy.get('.cm-line').type('\\cite{{}new') - // Wait a reasonable time to ensure the autocomplete would've appeared if there were any matches - // eslint-disable-next-line cypress/no-unnecessary-waiting - cy.wait(200) - cy.findByRole('listbox').should('not.exist') - cy.findByRole('treeitem', { name: 'sample.bib' }).click() - cy.get('.cm-line') - .last() - .type( - '\n@article{{}newkey2024,\n author = {{}Doe, John},\n title = {{}A New Article},\n journal = {{}Journal of Testing},\n year = 2024\n}\n' - ) - createNewFile() - cy.get('.cm-line').type('\\cite{{}new') - cy.findByRole('listbox').within(() => { - cy.findByRole('option').should('contain.text', 'newkey2024').click() - }) - cy.get('.cm-line').should('have.text', '\\cite{newkey2024}') - }) - }) - - describe('layout selector', function () { - it('show editor only and switch between editor and pdf', function () { - cy.findByRole('region', { name: 'PDF preview and logs' }).should( - 'be.visible' - ) - cy.get('.cm-editor').should('be.visible') - - cy.findByRole('button', { name: 'Layout' }).click() - cy.findByRole('menu').within(() => { - cy.findByRole('menuitem', { name: /Editor only/ }).click() - }) - - cy.findByRole('region', { name: 'PDF preview and logs' }).should( - 'not.be.visible' - ) - cy.get('.cm-editor').should('be.visible') - - cy.findByRole('button', { name: 'Switch to PDF' }).click() - - cy.findByRole('region', { name: 'PDF preview and logs' }).should( - 'be.visible' - ) - cy.get('.cm-editor').should('not.be.visible') - - cy.findByRole('button', { name: 'Switch to editor' }).click() - - cy.findByRole('region', { name: 'PDF preview and logs' }).should( - 'not.be.visible' - ) - cy.get('.cm-editor').should('be.visible') - }) - - it('show PDF only and go back to Editor & PDF', function () { - cy.findByRole('region', { name: 'PDF preview and logs' }).should( - 'be.visible' - ) - cy.get('.cm-editor').should('be.visible') - - cy.findByRole('button', { name: 'Layout' }).click() - cy.findByRole('menu').within(() => { - cy.findByRole('menuitem', { name: /PDF only/ }).click() - }) - - cy.findByRole('region', { name: 'PDF preview and logs' }).should( - 'be.visible' - ) - cy.get('.cm-editor').should('not.be.visible') - - cy.findByRole('button', { name: 'Layout' }).click() - cy.findByRole('menu').within(() => { - cy.findByRole('menuitem', { name: 'Editor & PDF' }).click() - }) - - cy.findByRole('region', { name: 'PDF preview and logs' }).should( - 'be.visible' - ) - cy.get('.cm-editor').should('be.visible') - }) - - it('PDF in a separate tab (tests editor only)', function () { - cy.findByTestId('pdf-viewer').should('be.visible') - cy.get('.cm-editor').should('be.visible') - - cy.findByRole('button', { name: 'Layout' }).click() - cy.findByRole('menu').within(() => { - cy.findByRole('menuitem', { name: 'PDF in separate tab' }).click() - }) - - cy.findByTestId('pdf-viewer').should('not.exist') - cy.get('.cm-editor').should('be.visible') - }) - }) - - describe('full project search', function () { - it('can search for text in project files', function () { - cy.get('button').contains('Search').click({ force: true }) - cy.findByRole('searchbox', { name: 'Search' }) - .should('be.visible') - .type('Some examples to get started') - cy.get('button').contains('Search').click() - - cy.findByRole('listbox').within(() => { - cy.findByRole('option', { - name: /Some examples to get started/, - }).should('be.visible') - }) - }) - }) -}) - -function createRandomLetterString() { - const chars = 'abcdefghijklmnopqrstuvwxyz' - let result = '' - for (let i = 0; i < 12; i++) { - result += chars.charAt(Math.floor(Math.random() * chars.length)) - } - return result -} diff --git a/server-ce/test/git-bridge.spec.ts b/server-ce/test/git-bridge.spec.ts deleted file mode 100644 index f20dd0d8fb..0000000000 --- a/server-ce/test/git-bridge.spec.ts +++ /dev/null @@ -1,492 +0,0 @@ -import { v4 as uuid } from 'uuid' -import { isExcludedBySharding, startWith } from './helpers/config' -import { ensureUserExists, login } from './helpers/login' -import { - createProject, - enableLinkSharing, - openProjectByName, - openProjectViaLinkSharingAsUser, - shareProjectByEmailAndAcceptInviteViaDash, -} from './helpers/project' - -import git from 'isomorphic-git' -import http from 'isomorphic-git/http/web' -import LightningFS from '@isomorphic-git/lightning-fs' -import { prepareWaitForNextCompileSlot } from './helpers/compile' - -const USER = 'user@example.com' - -describe('git-bridge', function () { - const ENABLED_VARS = { - GIT_BRIDGE_ENABLED: 'true', - GIT_BRIDGE_HOST: 'git-bridge', - GIT_BRIDGE_PORT: '8000', - V1_HISTORY_URL: 'http://sharelatex:3100/api', - } - - function gitURL(projectId: string) { - const url = new URL(Cypress.config().baseUrl!) - url.username = 'git' - url.pathname = `/git/${projectId}` - return url - } - - describe('enabled in Server Pro', function () { - if (isExcludedBySharding('PRO_CUSTOM_1')) return - startWith({ - pro: true, - vars: ENABLED_VARS, - }) - ensureUserExists({ email: USER }) - - function clearAllTokens() { - cy.findAllByRole('button', { name: 'Remove' }) - .not('[disabled]') - .each($button => { - cy.wrap($button).click() - cy.findByRole('button', { name: 'Delete token' }).click() - }) - cy.findByRole('dialog').should('not.exist') - } - - function maybeClearAllTokens() { - cy.visit('/user/settings') - cy.findByRole('heading', { name: 'Git integration' }) - cy.findByRole('button', { - name: /Generate token|Add another token/i, - }).then(btn => { - if (btn.text() === 'Add another token') { - clearAllTokens() - } - }) - } - - beforeEach(function () { - login(USER) - }) - - it('should render the git-bridge UI in the settings', function () { - maybeClearAllTokens() - cy.visit('/user/settings') - cy.findByRole('heading', { name: 'Git integration' }) - cy.findByRole('button', { - name: 'Git integration Generate token', - }).click() - cy.findByRole('dialog').within(() => { - cy.findByLabelText('Git authentication token') - .contains(/olp_[a-zA-Z0-9]{16}/) - .then(el => el.text()) - .as('newToken') - cy.findByRole('button', { name: 'Close dialog' }).click() - }) - cy.get('@newToken').then(token => { - // There can be more than one token with the same prefix when retrying - cy.findAllByText( - `${token.slice(0, 'olp_1234'.length)}${'*'.repeat(12)}` - ).should('have.length.at.least', 1) - }) - cy.findByRole('button', { - name: 'Git integration Generate token', - }).should('not.exist') - cy.findByRole('button', { name: 'Add another token' }).should('exist') - clearAllTokens() - cy.findByRole('button', { - name: 'Git integration Generate token', - }).should('exist') - cy.findByRole('button', { name: 'Add another token' }).should('not.exist') - }) - - it('should render the git-bridge UI in the editor', function () { - maybeClearAllTokens() - createProject('git').as('projectId') - cy.findByRole('navigation', { - name: 'Project actions', - }) - .findByRole('button', { name: 'Menu' }) - .click() - cy.findByTestId('left-menu').within(() => { - cy.findByRole('heading', { name: 'Sync' }) - cy.findByRole('button', { name: 'Git' }).click() - }) - cy.findByTestId('git-bridge-modal').within(() => { - cy.get('@projectId').then(id => { - cy.findByLabelText('Git clone project command').contains( - `git clone ${gitURL(id.toString())}` - ) - }) - cy.findByRole('button', { - name: 'Generate token', - }).click() - cy.findByLabelText('Git authentication token').contains( - /olp_[a-zA-Z0-9]{16}/ - ) - }) - - // Re-open - cy.url().then(url => cy.visit(url)) - cy.findByRole('navigation', { - name: 'Project actions', - }) - .findByRole('button', { name: 'Menu' }) - .click() - cy.findByTestId('left-menu').within(() => { - cy.findByRole('button', { name: 'Git' }).click() - }) - - cy.findByTestId('git-bridge-modal').within(() => { - cy.get('@projectId').then(id => { - cy.findByLabelText('Git clone project command').contains( - `git clone ${gitURL(id.toString())}` - ) - }) - cy.findByRole('button', { - name: 'Generate token', - }).should('not.exist') - cy.findByText(/generate a new one in Account settings/) - cy.findByRole('link', { name: 'Go to settings' }) - .should('have.attr', 'target', '_blank') - .and('have.attr', 'href', '/user/settings') - }) - }) - - describe('git access', function () { - ensureUserExists({ email: 'collaborator-rw@example.com' }) - ensureUserExists({ email: 'collaborator-ro@example.com' }) - ensureUserExists({ email: 'collaborator-link-rw@example.com' }) - ensureUserExists({ email: 'collaborator-link-ro@example.com' }) - - let projectName: string - let recompile: () => void - let waitForCompile: (triggerCompile: () => void) => void - beforeEach(function () { - projectName = uuid() - createProject(projectName, { open: false }).as('projectId') - ;({ recompile, waitForCompile } = prepareWaitForNextCompileSlot()) - }) - - it('should expose r/w interface to owner', function () { - maybeClearAllTokens() - waitForCompile(() => { - openProjectByName(projectName) - }) - checkGitAccess('readAndWrite') - }) - - it('should expose r/w interface to invited r/w collaborator', function () { - shareProjectByEmailAndAcceptInviteViaDash( - projectName, - 'collaborator-rw@example.com', - 'Editor' - ) - maybeClearAllTokens() - waitForCompile(() => { - openProjectByName(projectName) - }) - checkGitAccess('readAndWrite') - }) - - it('should expose r/o interface to invited r/o collaborator', function () { - shareProjectByEmailAndAcceptInviteViaDash( - projectName, - 'collaborator-ro@example.com', - 'Viewer' - ) - maybeClearAllTokens() - waitForCompile(() => { - openProjectByName(projectName) - }) - checkGitAccess('readOnly') - }) - - it('should expose r/w interface to link-sharing r/w collaborator', function () { - openProjectByName(projectName) - enableLinkSharing().then(({ linkSharingReadAndWrite }) => { - const email = 'collaborator-link-rw@example.com' - login(email) - maybeClearAllTokens() - waitForCompile(() => { - openProjectViaLinkSharingAsUser( - linkSharingReadAndWrite, - projectName, - email - ) - }) - checkGitAccess('readAndWrite') - }) - }) - - it('should expose r/o interface to link-sharing r/o collaborator', function () { - waitForCompile(() => { - openProjectByName(projectName) - }) - enableLinkSharing().then(({ linkSharingReadOnly }) => { - const email = 'collaborator-link-ro@example.com' - login(email) - maybeClearAllTokens() - waitForCompile(() => { - openProjectViaLinkSharingAsUser( - linkSharingReadOnly, - projectName, - email - ) - }) - checkGitAccess('readOnly') - }) - }) - - function checkGitAccess(access: 'readOnly' | 'readAndWrite') { - cy.findByRole('navigation', { - name: 'Project actions', - }) - .findByRole('button', { name: 'Menu' }) - .click() - cy.findByTestId('left-menu').within(() => { - cy.findByRole('heading', { name: 'Sync' }) - cy.findByRole('button', { name: 'Git' }).click() - }) - cy.get('@projectId').then(projectId => { - cy.findByTestId('git-bridge-modal').within(() => { - cy.findByLabelText('Git clone project command').contains( - `git clone ${gitURL(projectId.toString())}` - ) - cy.findByRole('heading', { name: 'Clone with Git' }) - cy.findByRole('button', { - name: 'Generate token', - }).click() - }) - cy.findByLabelText('Git authentication token') - .contains(/olp_[a-zA-Z0-9]{16}/) - .then(async tokenEl => { - const token = tokenEl.text() - - // close Git modal - cy.findByRole('button', { name: 'Close dialog' }).click() - cy.findByTestId('git-bridge-modal').should('not.exist') - // close the modal - cy.get('body').type('{esc}') - cy.findByTestId('left-menu').should('not.exist') - const fs = new LightningFS('fs') - const dir = `/${projectId}` - - async function readFile(path: string): Promise { - return await new Promise((resolve, reject) => { - fs.readFile(path, { encoding: 'utf8' }, (err, blob) => { - if (err) return reject(err) - resolve(blob as string) - }) - }) - } - - async function writeFile(path: string, data: string) { - return await new Promise((resolve, reject) => { - fs.writeFile(path, data, undefined, err => { - if (err) return reject(err) - resolve() - }) - }) - } - - const commonOptions = { - dir, - fs, - } - const url = gitURL(projectId.toString()) - url.username = '' // basic auth is specified separately. - const httpOptions = { - http, - url: url.toString(), - headers: { - Authorization: `Basic ${Buffer.from(`git:${token}`).toString('base64')}`, - }, - } - const authorOptions = { - author: { name: 'user', email: USER }, - committer: { name: 'user', email: USER }, - } - const mainTex = `${dir}/main.tex` - - // Clone - cy.then({ timeout: 10_000 }, async () => { - await git.clone({ - ...commonOptions, - ...httpOptions, - }) - }) - cy.findByText(/\\documentclass/) - .parent() - .parent() - .then(async editor => { - const onDisk = await readFile(mainTex) - expect(onDisk.replaceAll('\n', '')).to.equal(editor.text()) - }) - const text = ` -\\documentclass{article} -\\begin{document} -Hello world -\\end{document} -` - - // Make a change - cy.then(async () => { - await writeFile(mainTex, text) - await git.add({ - ...commonOptions, - filepath: 'main.tex', - }) - await git.commit({ - ...commonOptions, - ...authorOptions, - message: 'Swap main.tex', - }) - }) - - if (access === 'readAndWrite') { - // check history before push - cy.findByRole('navigation', { - name: 'Project actions', - }) - .findByRole('button', { name: 'History' }) - .click() - cy.findByRole('complementary', { - name: 'Project history and labels', - }).within(() => { - cy.findByText('(via Git)').should('not.exist') - }) - cy.findByRole('navigation', { - name: 'Project actions', - }) - .findByRole('button', { name: 'Back to editor' }) - .click() - cy.then(async () => { - await git.push({ - ...commonOptions, - ...httpOptions, - }) - }) - } else { - cy.then(async () => { - try { - await git.push({ - ...commonOptions, - ...httpOptions, - }) - expect.fail('push should have failed') - } catch (err) { - expect(err).to.match(/branches were not updated/) - expect(err).to.match(/forbidden/) - } - }) - - return // return early, below are write access bits - } - - // check push in editor - cy.findByText(/\\documentclass/) - .parent() - .parent() - .should('have.text', text.replaceAll('\n', '')) - - // Wait for history sync - trigger flush by toggling the UI - cy.findByRole('navigation', { - name: 'Project actions', - }).within(() => { - cy.findByRole('button', { name: 'History' }).click() - cy.findByRole('button', { name: 'Back to editor' }).click() - }) - - // check push in history - cy.findByRole('navigation', { - name: 'Project actions', - }) - .findByRole('button', { name: 'History' }) - .click() - cy.findByText(/Hello world/) - cy.findByRole('complementary', { - name: 'Project history and labels', - }).within(() => { - cy.findByText('(via Git)').should('exist') - }) - - // Back to the editor - cy.findByRole('navigation', { - name: 'Project actions', - }) - .findByRole('button', { name: 'Back to editor' }) - .click() - cy.findByText(/\\documentclass/) - .parent() - .parent() - .as('documentclass') - .click() - cy.get('@documentclass').type('% via editor{enter}') - - // Trigger flush via compile - recompile() - - // Back into the history, check what we just added - cy.findByRole('navigation', { - name: 'Project actions', - }) - .findByRole('button', { name: 'History' }) - .click() - cy.findByText(/% via editor/) - - // Pull the change - cy.then(async () => { - await git.pull({ - ...commonOptions, - ...httpOptions, - ...authorOptions, - }) - - expect(await readFile(mainTex)).to.equal( - text + '% via editor\n' - ) - }) - }) - }) - } - }) - }) - - function checkDisabled() { - ensureUserExists({ email: USER }) - - it('should not render the git-bridge UI in the settings', function () { - login(USER) - cy.visit('/user/settings') - cy.findByRole('heading', { name: 'Git integration' }).should('not.exist') - }) - it('should not render the git-bridge UI in the editor', function () { - login(USER) - createProject('maybe git') - cy.findByRole('navigation', { - name: 'Project actions', - }) - .findByRole('button', { name: 'Menu' }) - .click() - cy.findByTestId('left-menu').within(() => { - cy.findByRole('button', { name: 'Word Count' }) // wait for lazy loading - cy.findByRole('heading', { name: 'Sync' }).should('not.exist') - cy.findByRole('button', { name: 'Git' }).should('not.exist') - }) - }) - } - - describe('disabled in Server Pro', function () { - if (isExcludedBySharding('PRO_DEFAULT_1')) return - startWith({ - pro: true, - }) - checkDisabled() - }) - - describe('unavailable in CE', function () { - if (isExcludedBySharding('CE_CUSTOM_1')) return - startWith({ - pro: false, - vars: ENABLED_VARS, - }) - checkDisabled() - }) -}) diff --git a/server-ce/test/graceful-shutdown.spec.ts b/server-ce/test/graceful-shutdown.spec.ts deleted file mode 100644 index 6284cb243d..0000000000 --- a/server-ce/test/graceful-shutdown.spec.ts +++ /dev/null @@ -1,111 +0,0 @@ -import { ensureUserExists, login } from './helpers/login' -import { - isExcludedBySharding, - STARTUP_TIMEOUT, - startWith, -} from './helpers/config' -import { dockerCompose, getRedisKeys } from './helpers/hostAdminClient' -import { createProject } from './helpers/project' -import { prepareWaitForNextCompileSlot } from './helpers/compile' - -const USER = 'user@example.com' -const PROJECT_NAME = 'Old Project' - -function bringServerProBackUp() { - cy.log('bring server pro back up') - cy.then({ timeout: STARTUP_TIMEOUT }, async () => { - await dockerCompose('up', '--detach', '--wait', 'sharelatex') - }) -} - -describe('GracefulShutdown', function () { - if (isExcludedBySharding('PRO_CUSTOM_1')) return - startWith({ - pro: true, - withDataDir: true, - resetData: true, - }) - ensureUserExists({ email: USER }) - - let projectId: string - it('should display banner and flush changes out of redis', function () { - bringServerProBackUp() - login(USER) - const { recompile, waitForCompile } = prepareWaitForNextCompileSlot() - waitForCompile(() => { - createProject(PROJECT_NAME).then(id => { - projectId = id - }) - }) - - cy.log('add additional content') - cy.findByRole('region', { name: 'Editor' }).within(() => { - cy.findByText('\\maketitle').parent().click() - cy.findByText('\\maketitle').parent().type(`\n\\section{{}New Section}`) - }) - recompile() - - cy.log( - 'check flush from frontend to backend: should include new section in PDF' - ) - cy.findByRole('region', { name: 'PDF preview and logs' }).should( - 'contain.text', - 'New Section' - ) - - cy.log('should have unflushed content in redis before shutdown') - cy.then(async () => { - const keys = await getRedisKeys() - expect(keys).to.contain(`DocsIn:${projectId}`) - expect(keys).to.contain(`ProjectHistory:Ops:{${projectId}}`) - }) - - cy.log('trigger graceful shutdown') - let pendingShutdown: Promise - cy.then(() => { - pendingShutdown = dockerCompose('stop', '--timeout=60', 'sharelatex') - }) - - cy.log('wait for banner') - cy.findByRole('dialog').findByText(/performing maintenance/) - cy.log('wait for page reload') - cy.findByRole('heading', { name: 'Maintenance' }) - cy.findByText(/is currently down for maintenance/) - - cy.log('wait for shutdown to complete') - cy.then({ timeout: 60 * 1000 }, async () => { - await pendingShutdown - }) - - cy.log('should not have any unflushed content in redis after shutdown') - cy.then(async () => { - const keys = await getRedisKeys() - expect(keys).to.not.contain(`DocsIn:${projectId}`) - expect(keys).to.not.contain(`ProjectHistory:Ops:{${projectId}}`) - }) - - bringServerProBackUp() - - cy.then(() => { - cy.visit( - `/project/${projectId}?trick-cypress-into-page-reload=true&old-editor-override=true` - ) - }) - - cy.log('check loading doc from mongo') - cy.findByRole('region', { name: 'Editor' }).findByText('New Section') - - cy.log('check PDF') - cy.findByRole('region', { name: 'PDF preview and logs' }).should( - 'contain.text', - 'New Section' - ) - cy.log('check history') - cy.findByRole('navigation', { - name: 'Project actions', - }) - .findByRole('button', { name: 'History' }) - .click() - cy.findByText(/\\section\{New Section}/) - }) -}) diff --git a/server-ce/test/history.spec.ts b/server-ce/test/history.spec.ts deleted file mode 100644 index 7f146c758e..0000000000 --- a/server-ce/test/history.spec.ts +++ /dev/null @@ -1,164 +0,0 @@ -import { createProject } from './helpers/project' -import { prepareWaitForNextCompileSlot } from './helpers/compile' -import { ensureUserExists, login } from './helpers/login' -import { isExcludedBySharding, startWith } from './helpers/config' - -describe('History', function () { - if (isExcludedBySharding('CE_DEFAULT')) return - startWith({}) - ensureUserExists({ email: 'user@example.com' }) - beforeEach(function () { - login('user@example.com') - }) - - function addLabel(name: string) { - cy.log(`add label ${JSON.stringify(name)}`) - // The input is not clickable due to being visually hidden, click its label instead - cy.findByRole('complementary', { - name: 'Project history and labels', - }).within(() => { - cy.findByRole('group', { - name: 'Show all of the project history or only labelled versions.', - }).within(() => { - cy.findByText('Labels').click() - }) - cy.findByRole('radio', { name: 'Labels' }).should('be.checked') - cy.findByRole('radio', { name: 'All history' }).should('not.be.checked') - cy.findAllByTestId('history-version-details') - .first() - .within(() => { - cy.findByRole('button', { name: 'More actions' }).click() - cy.findByRole('menuitem', { name: 'Label this version' }).click() - }) - }) - cy.findByRole('dialog').within(() => { - cy.findByLabelText('New label name').type(`${name}{enter}`) - }) - } - - function downloadVersion(name: string) { - cy.log(`download version ${JSON.stringify(name)}`) - // The input is not clickable due to being visually hidden, click its label instead - cy.findByRole('complementary', { - name: 'Project history and labels', - }).within(() => { - cy.findByRole('group', { - name: 'Show all of the project history or only labelled versions.', - }).within(() => { - cy.findByText('Labels').click() - }) - cy.findByRole('radio', { name: 'Labels' }).should('be.checked') - cy.findByRole('radio', { name: 'All history' }).should('not.be.checked') - cy.findByText(name) - .closest('[data-testid="history-version-details"]') - .within(() => { - cy.findByRole('button', { name: 'More actions' }).click() - cy.findByRole('menuitem', { name: 'Download this version' }).click() - }) - }) - } - - const CLASS_ADDITION = 'ol-cm-addition-marker' - const CLASS_DELETION = 'ol-cm-deletion-marker' - - it('should support labels, comparison and download', function () { - const { recompile, waitForCompile } = prepareWaitForNextCompileSlot() - waitForCompile(() => { - createProject('labels') - }) - - cy.log('add content, including a line that will get removed soon') - cy.findByRole('textbox', { name: 'Source Editor editing' }).within(() => { - cy.findByText('\\maketitle').parent().click() - cy.findByText('\\maketitle').parent().type('\n% added') - cy.findByText('\\maketitle').parent().type('\n% to be removed') - }) - recompile() - cy.findByRole('button', { name: 'History' }).click() - - cy.log('expect to see additions in history') - cy.get('.document-diff-container').within(() => { - cy.findByText('% to be removed').should('have.class', CLASS_ADDITION) - cy.findByText('% added').should('have.class', CLASS_ADDITION) - }) - - addLabel('Before removal') - - cy.log('remove content') - cy.findByRole('button', { name: 'Back to editor' }).click() - cy.findByText('% to be removed').parent().type('{end}{shift}{upArrow}{del}') - recompile() - cy.findByRole('button', { name: 'History' }).click() - - cy.log('expect to see annotation for newly removed content in history') - cy.get('.document-diff-container').within(() => { - cy.findByText('% to be removed').should('have.class', CLASS_DELETION) - cy.findByText('% added').should('not.have.class', CLASS_ADDITION) - }) - - addLabel('After removal') - - cy.log('add more content after labeling') - cy.findByRole('button', { name: 'Back to editor' }).click() - cy.findByRole('textbox', { name: 'Source Editor editing' }).within(() => { - cy.findByText('\\maketitle').parent().click() - cy.findByText('\\maketitle').parent().type('\n% more') - }) - recompile() - - cy.log('compare non current versions') - cy.findByRole('button', { name: 'History' }).click() - // The input is not clickable due to being visually hidden, click its label instead - cy.findByRole('complementary', { - name: 'Project history and labels', - }).within(() => { - cy.findByRole('group', { - name: 'Show all of the project history or only labelled versions.', - }).within(() => { - cy.findByText('Labels').click() - }) - cy.findByRole('radio', { name: 'Labels' }).should('be.checked') - cy.findByRole('radio', { name: 'All history' }).should('not.be.checked') - cy.findAllByTestId('compare-icon-version').last().click() - cy.findAllByTestId('compare-icon-version').filter(':visible').click() - cy.findByRole('menuitem', { - name: 'Compare up to this version', - }).click() - }) - cy.log( - 'expect to see annotation for removed content between the two versions' - ) - cy.get('.document-diff-container').within(() => { - cy.findByText('% to be removed').should('have.class', CLASS_DELETION) - cy.findByText('% added').should('not.have.class', CLASS_ADDITION) - cy.findByText('% more').should('not.exist') - }) - - downloadVersion('Before removal') - cy.task('readFileInZip', { - pathToZip: `cypress/downloads/labels (Version 2).zip`, - fileToRead: 'main.tex', - }) - .should('contain', '% added') - .should('contain', '% to be removed') - .should('not.contain', '% more') - - downloadVersion('After removal') - cy.task('readFileInZip', { - pathToZip: `cypress/downloads/labels (Version 3).zip`, - fileToRead: 'main.tex', - }) - .should('contain', '% added') - .should('not.contain', '% to be removed') - .should('not.contain', '% more') - - downloadVersion('Current state') - cy.task('readFileInZip', { - pathToZip: `cypress/downloads/labels (Version 4).zip`, - fileToRead: 'main.tex', - }) - .should('contain', '% added') - .should('not.contain', '% to be removed') - .should('contain', '% more') - }) -}) diff --git a/server-ce/test/project-sharing.spec.ts b/server-ce/test/project-sharing.spec.ts deleted file mode 100644 index 2c6392bc31..0000000000 --- a/server-ce/test/project-sharing.spec.ts +++ /dev/null @@ -1,518 +0,0 @@ -import { v4 as uuid } from 'uuid' -import { - isExcludedBySharding, - startWith, - reloadWith, - STARTUP_TIMEOUT, -} from './helpers/config' -import { ensureUserExists, login } from './helpers/login' -import { - createProject, - enableLinkSharing, - getSpamSafeProjectName, - openProjectByName, - openProjectViaLinkSharingAsAnon, - openProjectViaLinkSharingAsUser, - shareProjectByEmailAndAcceptInviteViaDash, - shareProjectByEmailAndAcceptInviteViaEmail, -} from './helpers/project' -import { prepareWaitForNextCompileSlot } from './helpers/compile' -import { beforeWithReRunOnTestRetry } from './helpers/beforeWithReRunOnTestRetry' - -describe('Project Sharing', function () { - if (isExcludedBySharding('PRO_CUSTOM_4')) return - ensureUserExists({ email: 'user@example.com' }) - startWith({ withDataDir: true, pro: true }) - - let projectName: string - let recompile: () => void - let waitForCompile: (triggerCompile: () => void) => void - beforeWithReRunOnTestRetry(() => { - projectName = getSpamSafeProjectName() - ;({ recompile, waitForCompile } = prepareWaitForNextCompileSlot()) - setupTestProject() - }) - - beforeEach(function () { - // Always start with a fresh session - cy.session([uuid()], () => {}) - }) - - let linkSharingReadOnly: string - let linkSharingReadAndWrite: string - - function setupTestProject() { - login('user@example.com') - waitForCompile(() => { - createProject(projectName) - }) - - // Add chat message - cy.findByRole('button', { name: 'Chat' }).click() - // wait for lazy loading of the chat pane - cy.findByRole('complementary', { name: 'Chat' }).findByText( - 'Send your first message to your collaborators' - ) - cy.findByLabelText('Send a message to your collaborators…').type( - 'New Chat Message{enter}' - ) - - // Get link sharing links - enableLinkSharing().then( - ({ linkSharingReadOnly: ro, linkSharingReadAndWrite: rw }) => { - linkSharingReadAndWrite = rw - linkSharingReadOnly = ro - } - ) - } - - function expectContentReadOnlyAccess() { - cy.url().should('match', /\/project\/[a-fA-F0-9]{24}/) - cy.findByRole('textbox', { name: 'Source Editor editing' }).should( - 'contain.text', - '\\maketitle' - ) - cy.findByRole('textbox', { name: 'Source Editor editing' }).should( - 'have.attr', - 'contenteditable', - 'false' - ) - } - - function expectContentWriteAccess() { - const section = `Test Section ${uuid()}` - cy.url().should('match', /\/project\/[a-fA-F0-9]{24}/) - // wait for the editor to finish loading - cy.findByRole('textbox', { name: 'Source Editor editing' }).should( - 'contain.text', - '\\maketitle' - ) - // the editor should be writable - cy.findByRole('textbox', { name: 'Source Editor editing' }).should( - 'have.attr', - 'contenteditable', - 'true' - ) - cy.findByRole('textbox', { name: 'Source Editor editing' }).within(() => { - cy.findByText('\\maketitle').parent().click() - cy.findByText('\\maketitle').parent().type(`\n\\section{{}${section}}`) - }) - // should have written - cy.findByRole('textbox', { name: 'Source Editor editing' }).should( - 'contain.text', - `\\section{${section}}` - ) - // check PDF - recompile() - cy.findByRole('region', { name: 'PDF preview and logs' }).within(() => { - cy.findByLabelText(/Page.*1/i).should('be.visible') - cy.findByText(projectName).should('be.visible') - }) - cy.findByRole('region', { name: 'PDF preview and logs' }).within(() => { - cy.findByLabelText(/Page.*1/i).should('be.visible') - cy.contains(section) - }) - } - - function expectNoAccess() { - // try read only access link - cy.visit(linkSharingReadOnly) - cy.url().should('match', /\/login/) - - // Cypress bugs: cypress resolves the link-sharing link outside the browser, and it carries over the hash of the link-sharing link to the login page redirect (bug 1). - // Effectively, cypress then instructs the browser to change the page from /login#read-only-hash to /login#read-and-write-hash. - // This is turn does not trigger a "page load", but rather just "scrolling", which in turn trips up the "page loaded" detection in cypress (bug 2). - // Work around this by navigating away from the /login page in between checks. - cy.visit('/user/password/reset') - - // try read and write access link - cy.visit(linkSharingReadAndWrite) - cy.url().should('match', /\/login/) - } - - function expectChatAccess() { - cy.findByRole('button', { name: 'Chat' }).click() - cy.findByRole('complementary', { name: 'Chat' }).findByText( - 'New Chat Message' - ) - } - - function expectHistoryAccess() { - cy.findByRole('button', { name: 'History' }).click() - // The input is not clickable due to being visually hidden, click its label instead - cy.findByRole('complementary', { - name: 'Project history and labels', - }).within(() => { - cy.findByRole('group', { - name: 'Show all of the project history or only labelled versions.', - }).within(() => { - cy.findByText('All history').click() - }) - cy.findByRole('radio', { name: 'Labels' }).should('not.be.checked') - cy.findByRole('radio', { name: 'All history' }).should('be.checked') - }) - cy.findByText(/\\begin\{document}/) - cy.findByRole('complementary', { - name: 'Project history and labels', - }).within(() => { - cy.findAllByTestId('history-version-metadata-users') - .last() - .should('have.text', 'user') - }) - cy.findByRole('button', { name: 'Back to editor' }).click() - } - - function expectNoChatAccess() { - cy.findByRole('button', { name: 'Layout' }) // wait for lazy loading - cy.findByRole('button', { name: 'Chat' }).should('not.exist') - } - - function expectNoHistoryAccess() { - cy.findByRole('button', { name: 'Layout' }) // wait for lazy loading - cy.findByRole('button', { name: 'History' }).should('not.exist') - } - - function expectCommentAccess() { - cy.findByRole('textbox', { name: 'Source Editor editing' }).should( - 'contain.text', - '\\maketitle' - ) - - cy.findByText('\\maketitle').parent().dblclick() - - cy.findByRole('button', { name: 'Add comment' }).should('be.visible') - - cy.findByRole('textbox', { name: 'Source Editor editing' }).click() - } - - function expectNoCommentAccess() { - cy.findByRole('textbox', { name: 'Source Editor editing' }).should( - 'contain.text', - '\\maketitle' - ) - - cy.findByText('\\maketitle').parent().dblclick() - - cy.findByRole('button', { name: 'Add comment' }).should('not.exist') - cy.findByRole('textbox', { name: 'Source Editor editing' }).click() - } - - function expectFullReadOnlyAccess() { - expectContentReadOnlyAccess() - expectChatAccess() - expectHistoryAccess() - expectNoCommentAccess() - } - - function expectRestrictedReadOnlyAccess() { - expectContentReadOnlyAccess() - expectNoChatAccess() - expectNoHistoryAccess() - expectNoCommentAccess() - } - - function expectFullReadAndWriteAccess() { - expectContentWriteAccess() - expectChatAccess() - expectHistoryAccess() - expectCommentAccess() - } - - function expectAnonymousReadAndWriteAccess() { - expectContentWriteAccess() - expectChatAccess() - expectHistoryAccess() - expectNoCommentAccess() - } - - function expectProjectDashboardEntry() { - cy.visit('/project') - cy.findByText(projectName) - } - - function expectEditAuthoredAs(author: string) { - cy.findByRole('button', { name: 'History' }).click() - cy.findByRole('complementary', { - name: 'Project history and labels', - }).within(() => { - cy.findAllByTestId('history-version-metadata-users') - .first() - .should('contain.text', author) // might have other edits in the same group - }) - } - describe('via email', function () { - const email = 'collaborator-email@example.com' - ensureUserExists({ email }) - - beforeEach(function () { - login('user@example.com') - shareProjectByEmailAndAcceptInviteViaEmail(projectName, email, 'Viewer') - }) - - it('should grant the collaborator read access', function () { - expectFullReadOnlyAccess() - expectProjectDashboardEntry() - }) - }) - - describe('read only', function () { - const email = 'collaborator-ro@example.com' - ensureUserExists({ email }) - - beforeWithReRunOnTestRetry(() => { - login('user@example.com') - shareProjectByEmailAndAcceptInviteViaDash(projectName, email, 'Viewer') - }) - - it('should grant the collaborator read access', function () { - login(email) - openProjectByName(projectName) - expectFullReadOnlyAccess() - expectProjectDashboardEntry() - }) - }) - - describe('read and write', function () { - const email = 'collaborator-rw@example.com' - ensureUserExists({ email }) - - beforeWithReRunOnTestRetry(() => { - login('user@example.com') - shareProjectByEmailAndAcceptInviteViaDash(projectName, email, 'Editor') - }) - - it('should grant the collaborator write access', function () { - login(email) - openProjectByName(projectName) - expectFullReadAndWriteAccess() - expectEditAuthoredAs('You') - expectProjectDashboardEntry() - }) - }) - - describe('token access', function () { - describe('logged in', function () { - describe('read only', function () { - const email = 'collaborator-link-ro@example.com' - ensureUserExists({ email }) - - it('should grant restricted read access', function () { - login(email) - openProjectViaLinkSharingAsUser( - linkSharingReadOnly, - projectName, - email - ) - expectRestrictedReadOnlyAccess() - expectProjectDashboardEntry() - }) - }) - - describe('read and write', function () { - const email = 'collaborator-link-rw@example.com' - ensureUserExists({ email }) - - it('should grant full write access', function () { - login(email) - openProjectViaLinkSharingAsUser( - linkSharingReadAndWrite, - projectName, - email - ) - expectFullReadAndWriteAccess() - expectEditAuthoredAs('You') - expectProjectDashboardEntry() - }) - }) - }) - - describe('with OVERLEAF_ALLOW_PUBLIC_ACCESS=false', function () { - describe('wrap startup', function () { - startWith({ - pro: true, - vars: { - OVERLEAF_ALLOW_PUBLIC_ACCESS: 'false', - }, - withDataDir: true, - }) - it('should block access', function () { - expectNoAccess() - }) - }) - - describe('with OVERLEAF_ALLOW_ANONYMOUS_READ_AND_WRITE_SHARING=true', function () { - startWith({ - pro: true, - vars: { - OVERLEAF_ALLOW_PUBLIC_ACCESS: 'false', - OVERLEAF_ALLOW_ANONYMOUS_READ_AND_WRITE_SHARING: 'true', - }, - withDataDir: true, - }) - it('should block access', function () { - expectNoAccess() - }) - }) - }) - - describe('with OVERLEAF_ALLOW_PUBLIC_ACCESS=true', function () { - describe('wrap startup', function () { - startWith({ - pro: true, - vars: { - OVERLEAF_ALLOW_PUBLIC_ACCESS: 'true', - }, - withDataDir: true, - }) - it('should grant read access with read link', function () { - openProjectViaLinkSharingAsAnon(linkSharingReadOnly) - expectRestrictedReadOnlyAccess() - }) - - it('should prompt for login with write link', function () { - cy.visit(linkSharingReadAndWrite) - cy.url().should('match', /\/login/) - }) - }) - - describe('with OVERLEAF_ALLOW_ANONYMOUS_READ_AND_WRITE_SHARING=true', function () { - startWith({ - pro: true, - vars: { - OVERLEAF_ALLOW_PUBLIC_ACCESS: 'true', - OVERLEAF_ALLOW_ANONYMOUS_READ_AND_WRITE_SHARING: 'true', - }, - withDataDir: true, - }) - - it('should grant read access with read link', function () { - openProjectViaLinkSharingAsAnon(linkSharingReadOnly) - expectRestrictedReadOnlyAccess() - }) - - it('should grant write access with write link', function () { - openProjectViaLinkSharingAsAnon(linkSharingReadAndWrite) - expectAnonymousReadAndWriteAccess() - expectEditAuthoredAs('Anonymous') - }) - }) - }) - - describe('with OVERLEAF_DISABLE_LINK_SHARING=true', function () { - const email = 'collaborator-email@example.com' - ensureUserExists({ email }) - - const invitedEmail = 'invited-email@example.com' - ensureUserExists({ email: invitedEmail }) - - const retainedViewerEmail = 'collaborator-retained-viewer@example.com' - ensureUserExists({ email: retainedViewerEmail }) - - const retainedEditorEmail = 'collaborator-retained-editor@example.com' - ensureUserExists({ email: retainedEditorEmail }) - - // Link-sharing urls have to be created before disabling link sharing. - // We use the `beforeEach` hook to reload the server with link sharing - // disabled **after** the initial setup which happens in the `before` - // block. The `before` hook always runs prior to the `beforeEach` hook. - - // Set up retained access before disabling link sharing - before(function () { - // Set up retained viewer access - login(retainedViewerEmail) - openProjectViaLinkSharingAsUser( - linkSharingReadOnly, - projectName, - retainedViewerEmail - ) - - // Set up retained editor access - login(retainedEditorEmail) - openProjectViaLinkSharingAsUser( - linkSharingReadAndWrite, - projectName, - retainedEditorEmail - ) - }) - - beforeEach(function () { - this.timeout(STARTUP_TIMEOUT) // Increase timeout for server reload - - return cy.wrap( - reloadWith({ - pro: true, - vars: { - OVERLEAF_ALLOW_PUBLIC_ACCESS: 'true', - OVERLEAF_ALLOW_ANONYMOUS_READ_AND_WRITE_SHARING: 'true', - OVERLEAF_DISABLE_LINK_SHARING: 'true', - }, - withDataDir: true, - }), - { timeout: STARTUP_TIMEOUT } - ) - }) - - it('should not display link sharing in the sharing modal', function () { - login('user@example.com') - openProjectByName(projectName) - cy.findByRole('navigation', { - name: 'Project actions', - }) - .findByRole('button', { name: 'Share' }) - .click() - cy.findByRole('button', { name: 'Turn on link sharing' }).should( - 'not.exist' - ) - }) - - it('should block new access to read-only link shared projects', function () { - login(email) - - // Test read-only link returns 404 - cy.request({ - url: linkSharingReadOnly, - failOnStatusCode: false, - }).then(response => { - expect(response.status).to.eq(404) - }) - }) - - it('should block new access to read-write link shared projects', function () { - login(email) - - // Test read-write link returns 404 - cy.request({ - url: linkSharingReadAndWrite, - failOnStatusCode: false, - }).then(response => { - expect(response.status).to.eq(404) - }) - }) - - it('should continue to allow email sharing', function () { - login('user@example.com') - shareProjectByEmailAndAcceptInviteViaEmail( - projectName, - invitedEmail, - 'Viewer' - ) - expectFullReadOnlyAccess() - expectProjectDashboardEntry() - }) - - it('should retain read-only access when project was joined via link before link sharing was turned off', function () { - login(retainedViewerEmail) - openProjectByName(projectName) - expectRestrictedReadOnlyAccess() - expectProjectDashboardEntry() - }) - - it('should retain read-write access when project was joined via link before link sharing was turned off', function () { - login(retainedEditorEmail) - openProjectByName(projectName) - expectFullReadAndWriteAccess() - expectProjectDashboardEntry() - }) - }) - }) -}) diff --git a/server-ce/test/sandboxed-compiles.spec.ts b/server-ce/test/sandboxed-compiles.spec.ts deleted file mode 100644 index 33fd4208a3..0000000000 --- a/server-ce/test/sandboxed-compiles.spec.ts +++ /dev/null @@ -1,352 +0,0 @@ -import { ensureUserExists, login } from './helpers/login' -import { createProject } from './helpers/project' -import { isExcludedBySharding, startWith } from './helpers/config' -import { prepareWaitForNextCompileSlot, stopCompile } from './helpers/compile' -import { v4 as uuid } from 'uuid' -import { waitUntilScrollingFinished } from './helpers/waitUntilScrollingFinished' - -const LABEL_TEX_LIVE_VERSION = 'TeX Live version' - -describe('SandboxedCompiles', function () { - const enabledVars = { - SANDBOXED_COMPILES: 'true', - ALL_TEX_LIVE_DOCKER_IMAGE_NAMES: '2023,2022', - } - - describe('enabled in Server Pro', function () { - if (isExcludedBySharding('PRO_CUSTOM_2')) return - startWith({ - pro: true, - vars: enabledVars, - resetData: true, - }) - ensureUserExists({ email: 'user@example.com' }) - beforeEach(function () { - login('user@example.com') - }) - - it('should offer TexLive images and switch the compiler', function () { - const { recompile, waitForCompile } = prepareWaitForNextCompileSlot() - waitForCompile(() => { - createProject('sandboxed') - }) - cy.log('wait for compile') - cy.findByRole('region', { name: 'PDF preview and logs' }).should( - 'contain.text', - 'sandboxed' - ) - - cy.log('Check which compiler version was used, expect 2023') - cy.findByRole('button', { name: 'View logs' }).click() - cy.findByLabelText('Raw logs from the LaTeX compiler').findByText( - /This is pdfTeX, Version .+ \(TeX Live 2023\) / - ) - - cy.log('Switch TeXLive version from 2023 to 2022') - cy.findByRole('navigation', { - name: 'Project actions', - }) - .findByRole('button', { name: 'Menu' }) - .click() - cy.findByRole('dialog').within(() => { - cy.findByRole('option', { name: '2023' }).should('be.selected') - cy.findByRole('combobox', { name: LABEL_TEX_LIVE_VERSION }).select( - '2022' - ) - }) - cy.get('body').type('{esc}') - cy.findByRole('dialog').should('not.exist') - cy.log('Trigger compile with other TeX Live version') - recompile() - - cy.log('Check which compiler version was used, expect 2022') - cy.findByRole('button', { name: 'View logs' }).click() - cy.findByLabelText('Raw logs from the LaTeX compiler').findByText( - /This is pdfTeX, Version .+ \(TeX Live 2022\) / - ) - }) - - checkSyncTeX() - checkXeTeX() - checkRecompilesAfterErrors() - checkStopCompile() - }) - - function checkStopCompile() { - it('users can stop a running compile', function () { - login('user@example.com') - const { recompile, waitForCompile, waitForCompileRateLimitCoolOff } = - prepareWaitForNextCompileSlot() - waitForCompile(() => { - createProject('test-project') - }) - // create an infinite loop in the main document - // this will cause the compile to run indefinitely - cy.findByText('\\maketitle').parent().click() - cy.findByText('\\maketitle') - .parent() - .type('\n\\def\\x{{}Hello!\\par\\x}\\x') - waitForCompileRateLimitCoolOff() - cy.log('Start compile') - // We need to start the compile manually because we do not want to wait for it to finish - cy.findByRole('button', { name: 'Recompile' }).click() - // Now stop the compile and kill the latex process - stopCompile({ delay: 1000 }) - cy.findByRole('region', { name: 'PDF preview and logs' }) - .invoke('text') - .should('match', /PDF Rendering Error|Compilation cancelled/) - // Check that the previous compile is not running in the background by - // disabling the infinite loop and recompiling - cy.findByText('\\def').parent().click() - cy.findByText('\\def').parent().type('{home}disabled loop% ') - recompile() - cy.findByRole('region', { name: 'PDF preview and logs' }) - .should('contain.text', 'disabled loop') - .should('not.contain.text', 'A previous compile is still running') - }) - } - - function checkSyncTeX() { - describe('SyncTeX', function () { - let projectName: string - beforeEach(function () { - projectName = `Project ${uuid()}` - const { recompile, waitForCompile } = prepareWaitForNextCompileSlot() - waitForCompile(() => { - createProject(projectName) - }) - cy.findByRole('textbox', { name: 'Source Editor editing' }).within( - () => { - cy.findByText('\\maketitle').parent().click() - cy.findByText('\\maketitle') - .parent() - .type( - `\n\\pagebreak\n\\section{{}Section A}\n\\pagebreak\n\\section{{}Section B}\n\\pagebreak` - ) - } - ) - recompile() - cy.log('wait for pdf-rendering') - cy.findByRole('region', { name: 'PDF preview and logs' }).findByText( - projectName - ) - }) - - it('should sync to code', function () { - cy.log('navigate to \\maketitle using double click in PDF') - cy.findByRole('region', { name: 'PDF preview and logs' }) - .findByText(projectName) - .dblclick() - cy.get('.cm-activeLine').should('have.text', '\\maketitle') - - cy.log('navigate to Section A using double click in PDF') - cy.findByRole('region', { name: 'PDF preview and logs' }) - .findByText('Section A') - .dblclick() - cy.get('.cm-activeLine').should('have.text', '\\section{Section A}') - - cy.log('navigate to Section B using arrow button') - cy.findByTestId('pdfjs-viewer-inner') - .should('have.prop', 'scrollTop') - .as('start') - cy.findByRole('region', { name: 'PDF preview and logs' }) - .findByText('Section B') - .scrollIntoView() - cy.get('@start').then((start: any) => { - waitUntilScrollingFinished( - '[data-testid="pdfjs-viewer-inner"]', - start - ) - }) - // The sync button is swapped as the position in the PDF changes. - // Cypress appears to click on a button that references a stale position. - // Adding a cy.wait() statement is the most reliable "fix" so far :/ - // eslint-disable-next-line cypress/no-unnecessary-waiting - cy.wait(1000) - cy.findByRole('button', { - name: 'Go to PDF location in code (Tip: double click on the PDF for best results)', - }).click() - cy.get('.cm-activeLine').should('have.text', '\\section{Section B}') - }) - - it('should sync to pdf', function () { - cy.log('zoom in') - cy.findByRole('button', { name: 'PDF zoom level' }).click() - cy.findByRole('menuitem', { name: '400%' }).click() - cy.log('scroll to top') - cy.findByTestId('pdfjs-viewer-inner').scrollTo('top') - waitUntilScrollingFinished('[data-testid="pdfjs-viewer-inner"]', -1).as( - 'start' - ) - - cy.log('navigate to title') - cy.findByRole('textbox', { name: 'Source Editor editing' }).within( - () => { - cy.findByText('\\maketitle').parent().click() - } - ) - cy.findByRole('button', { name: 'Go to code location in PDF' }).click() - cy.get('@start').then((start: any) => { - waitUntilScrollingFinished( - '[data-testid="pdfjs-viewer-inner"]', - start - ) - .as('title') - .should('be.greaterThan', start) - }) - - cy.log('navigate to Section A') - cy.findByRole('textbox', { name: 'Source Editor editing' }).within(() => - cy.findByText('Section A').click() - ) - cy.findByRole('button', { name: 'Go to code location in PDF' }).click() - cy.get('@title').then((title: any) => { - waitUntilScrollingFinished( - '[data-testid="pdfjs-viewer-inner"]', - title - ) - .as('sectionA') - .should('be.greaterThan', title) - }) - - cy.log('navigate to Section B') - cy.findByRole('textbox', { name: 'Source Editor editing' }).within(() => - cy.findByText('Section B').click() - ) - cy.findByRole('button', { name: 'Go to code location in PDF' }).click() - cy.get('@sectionA').then((title: any) => { - waitUntilScrollingFinished( - '[data-testid="pdfjs-viewer-inner"]', - title - ) - .as('sectionB') - .should('be.greaterThan', title) - }) - }) - }) - } - - function checkRecompilesAfterErrors() { - it('recompiles even if there are Latex errors', function () { - login('user@example.com') - const { recompile, waitForCompile } = prepareWaitForNextCompileSlot() - waitForCompile(() => { - createProject('test-project') - }) - cy.findByRole('textbox', { name: 'Source Editor editing' }).within(() => { - cy.findByText('\\maketitle').parent().click() - cy.findByText('\\maketitle') - .parent() - .type('\n\\fakeCommand{} \n\\section{{}Test Section}') - }) - recompile() - recompile() - cy.findByRole('region', { name: 'PDF preview and logs' }) - .findByText('Test Section') - .should('not.contain.text', 'No PDF') - }) - } - - function checkXeTeX() { - it('should be able to use XeLaTeX', function () { - const { recompile, waitForCompile } = prepareWaitForNextCompileSlot() - waitForCompile(() => { - createProject('XeLaTeX') - }) - cy.log('wait for compile') - cy.findByRole('region', { name: 'PDF preview and logs' }).findByText( - 'XeLaTeX' - ) - cy.log('Check which compiler was used, expect pdfLaTeX') - cy.findByRole('button', { name: 'View logs' }).click() - cy.findByLabelText('Raw logs from the LaTeX compiler').findByText( - /This is pdfTeX/ - ) - - cy.log('Switch compiler to from pdfLaTeX to XeLaTeX') - cy.findByRole('navigation', { - name: 'Project actions', - }) - .findByRole('button', { name: 'Menu' }) - .click() - cy.findByRole('dialog').within(() => { - cy.findByRole('option', { name: 'pdfLaTeX' }).should('be.selected') - cy.findByRole('combobox', { name: 'Compiler' }).select('XeLaTeX') - }) - cy.get('body').type('{esc}') - cy.findByRole('dialog').should('not.exist') - - cy.log('Trigger compile with other compiler') - recompile() - - cy.log('Check which compiler was used, expect XeLaTeX') - cy.findByRole('button', { name: 'View logs' }).click() - cy.findByLabelText('Raw logs from the LaTeX compiler').findByText( - /This is XeTeX/ - ) - }) - } - - function checkUsesDefaultCompiler() { - beforeEach(function () { - login('user@example.com') - }) - - it('should not offer TexLive images and use default compiler', function () { - createProject('sandboxed') - cy.log('wait for compile') - cy.findByRole('region', { name: 'PDF preview and logs' }).findByText( - 'sandboxed' - ) - - cy.log('Check which compiler version was used, expect 2025') - cy.findByRole('button', { name: 'View logs' }).click() - cy.findByLabelText('Raw logs from the LaTeX compiler').findByText( - /This is pdfTeX, Version .+ \(TeX Live 2025\) / - ) - - cy.log('Check that there is no TeX Live version toggle') - cy.findByRole('navigation', { - name: 'Project actions', - }) - .findByRole('button', { name: 'Menu' }) - .click() - cy.findByTestId('left-menu').within(() => { - cy.findByRole('button', { name: 'Word Count' }) // wait for lazy loading - cy.findByText(LABEL_TEX_LIVE_VERSION).should('not.exist') - }) - }) - } - - describe('disabled in Server Pro', function () { - if (isExcludedBySharding('PRO_DEFAULT_2')) return - startWith({ pro: true }) - ensureUserExists({ email: 'user@example.com' }) - beforeEach(function () { - login('user@example.com') - }) - - checkUsesDefaultCompiler() - checkSyncTeX() - checkXeTeX() - checkRecompilesAfterErrors() - checkStopCompile() - }) - - // https://github.com/overleaf/internal/issues/20216 - // eslint-disable-next-line mocha/no-skipped-tests - describe.skip('unavailable in CE', function () { - if (isExcludedBySharding('CE_CUSTOM_1')) return - startWith({ pro: false, vars: enabledVars, resetData: true }) - ensureUserExists({ email: 'user@example.com' }) - beforeEach(function () { - login('user@example.com') - }) - - checkUsesDefaultCompiler() - checkSyncTeX() - checkXeTeX() - checkRecompilesAfterErrors() - checkStopCompile() - }) -}) diff --git a/server-ce/test/templates.spec.ts b/server-ce/test/templates.spec.ts deleted file mode 100644 index 5493f1fa67..0000000000 --- a/server-ce/test/templates.spec.ts +++ /dev/null @@ -1,280 +0,0 @@ -import { isExcludedBySharding, startWith } from './helpers/config' -import { ensureUserExists, login } from './helpers/login' -import { - createProject, - NEW_PROJECT_BUTTON_MATCHER, - redirectEditorUrlWithQueryParams, -} from './helpers/project' - -const WITHOUT_PROJECTS_USER = 'user-without-projects@example.com' -const ADMIN_USER = 'admin@example.com' -const REGULAR_USER = 'user@example.com' -const TEMPLATES_USER = 'templates@example.com' - -// Re-use value for "exists" and "does not exist" tests -const LABEL_BROWSE_TEMPLATES = 'Browse templates' - -describe('Templates', function () { - ensureUserExists({ email: TEMPLATES_USER }) - ensureUserExists({ email: WITHOUT_PROJECTS_USER }) - - let OVERLEAF_TEMPLATES_USER_ID: string - before(function () { - login(TEMPLATES_USER) - cy.visit('/') - cy.get('meta[name="ol-user_id"]').then(el => { - OVERLEAF_TEMPLATES_USER_ID = el.attr('content')! - }) - }) - - function varsFn() { - return { - OVERLEAF_TEMPLATES_USER_ID, - OVERLEAF_NEW_PROJECT_TEMPLATE_LINKS: - '[{"name":"All Templates","url":"/templates/all"}]', - } - } - - describe('enabled in Server Pro', function () { - if (isExcludedBySharding('PRO_CUSTOM_2')) return - startWith({ - pro: true, - varsFn, - }) - ensureUserExists({ email: REGULAR_USER }) - ensureUserExists({ email: ADMIN_USER, isAdmin: true }) - - it('should show templates link on welcome page', function () { - login(WITHOUT_PROJECTS_USER) - cy.visit('/') - cy.findByRole('link', { name: LABEL_BROWSE_TEMPLATES }).click() - cy.url().should('match', /\/templates$/) - }) - - it('should have templates feature', function () { - login(TEMPLATES_USER) - const name = `Template ${Date.now()}` - const description = `Template Description ${Date.now()}` - - cy.visit('/') - createProject(name, { type: 'Example project' }).as('templateProjectId') - - cy.findByRole('navigation', { - name: 'Project actions', - }) - .findByRole('button', { name: 'Menu' }) - .click() - cy.findByRole('button', { name: 'Manage Template' }).click() - - cy.findByLabelText('Template Description').type(description) - cy.findByRole('button', { name: 'Publish' }).click() - cy.findByRole('button', { name: 'Publishing…' }).should('be.disabled') - cy.findByRole('button', { name: 'Publish' }).should('not.exist') - cy.findByRole('button', { name: 'Unpublish', timeout: 60_000 }) - cy.findByRole('button', { name: 'Republish' }) - - cy.findByRole('link', { name: 'View it in the template gallery' }).click() - cy.url() - .should('match', /\/templates\/[a-f0-9]{24}$/) - .as('templateURL') - - cy.findByRole('heading', { level: 2 }).findByText(name) - cy.findByText(description) - cy.findByRole('link', { name: 'Open as Template' }) - cy.findByRole('button', { name: 'Unpublish' }) - cy.findByRole('button', { name: 'Republish' }) - cy.get('img') - .should('have.attr', 'src') - .and('match', /\/v\/0\//) - cy.findByRole('button', { name: 'Republish' }).click() - cy.findByRole('button', { name: 'Publishing…' }).should('be.disabled') - cy.findByRole('button', { name: 'Republish', timeout: 60_000 }) - cy.get('img', { timeout: 60_000 }) - .should('have.attr', 'src') - .and('match', /\/v\/1\//) - - // custom tag - const tagName = `${Date.now()}` - cy.visit('/') - cy.findByRole('checkbox', { name: `Select ${name}` }).check() - cy.findByRole('navigation', { name: 'Project categories and tags' }) - .findByRole('button', { name: 'New tag' }) - .click() - cy.focused().type(tagName) - cy.findByRole('button', { name: 'Create' }).click() - cy.findByRole('navigation', { - name: 'Project categories and tags', - }).should('contain', `${tagName} (1)`) - - // Check listing - cy.visit('/templates') - cy.findByRole('link', { name: tagName }) - cy.visit('/templates/all') - cy.findByRole('heading', { name }) - cy.visit(`/templates/${tagName}`) - cy.findByRole('heading', { name }) - - // Unpublish via template page - cy.get('@templateURL').then(url => cy.visit(`${url}`)) - cy.findByRole('button', { name: 'Unpublish' }).click() - cy.url().should('match', /\/templates$/) - cy.get('@templateURL').then(url => - cy.visit(`${url}`, { - failOnStatusCode: false, - }) - ) - cy.findByRole('heading', { name: 'Not found' }) - cy.visit('/templates/all') - cy.findByRole('heading', { name }).should('not.exist') - cy.visit(`/templates/${tagName}`) - cy.findByRole('heading', { name }).should('not.exist') - - // Publish again - redirectEditorUrlWithQueryParams(false) - cy.get('@templateProjectId').then(projectId => - cy.visit(`/project/${projectId}`) - ) - cy.findByRole('navigation', { - name: 'Project actions', - }) - .findByRole('button', { name: 'Menu' }) - .click() - cy.findByRole('button', { name: 'Manage Template' }).click() - cy.findByRole('button', { name: 'Publish' }).click() - cy.findByRole('button', { name: 'Unpublish', timeout: 60_000 }) - - // Should assign a new template id - cy.findByRole('link', { name: 'View it in the template gallery' }).click() - cy.url() - .should('match', /\/templates\/[a-f0-9]{24}$/) - .as('newTemplateURL') - cy.get('@newTemplateURL').then(newURL => { - cy.get('@templateURL').then(prevURL => { - expect(newURL).to.match(/\/templates\/[a-f0-9]{24}$/) - expect(prevURL).to.not.equal(newURL) - }) - }) - - // Open project from template - login(REGULAR_USER) - cy.visit('/templates') - cy.findByRole('link', { name: tagName }).click() - cy.findByRole('link', { name }).click() - redirectEditorUrlWithQueryParams(false) - cy.findByRole('link', { name: 'Open as Template' }).click() - cy.findByRole('navigation', { name: 'Project actions' }).findByText( - /Your Paper/i - ) // might have (1) suffix - cy.findByRole('navigation', { - name: 'Project actions', - }) - .findByRole('button', { name: 'Menu' }) - .click() - cy.findByRole('button', { name: 'Word Count' }).click() // wait for lazy loading - cy.findByRole('button', { name: 'Manage Template' }).should('not.exist') - - // Check management as regular user - cy.get('@newTemplateURL').then(url => cy.visit(`${url}`)) - cy.findByRole('link', { name: 'Open as Template' }) - cy.findByRole('button', { name: 'Unpublish' }).should('not.exist') - cy.findByRole('button', { name: 'Republish' }).should('not.exist') - - // Check management as admin user - login(ADMIN_USER) - cy.get('@newTemplateURL').then(url => cy.visit(`${url}`)) - cy.findByRole('link', { name: 'Open as Template' }) - cy.findByRole('button', { name: 'Unpublish' }) - cy.findByRole('button', { name: 'Republish' }) - redirectEditorUrlWithQueryParams(false) - cy.get('@templateProjectId').then(projectId => - cy.visit(`/project/${projectId}`) - ) - cy.findByRole('navigation', { - name: 'Project actions', - }) - .findByRole('button', { name: 'Menu' }) - .click() - cy.findByRole('button', { name: 'Manage Template' }).click() - cy.findByRole('button', { name: 'Unpublish' }) - - // Back to templates user - login(TEMPLATES_USER) - - // Unpublish via editor - redirectEditorUrlWithQueryParams(false) - cy.get('@templateProjectId').then(projectId => - cy.visit(`/project/${projectId}`) - ) - cy.findByRole('navigation', { - name: 'Project actions', - }) - .findByRole('button', { name: 'Menu' }) - .click() - cy.findByRole('button', { name: 'Manage Template' }).click() - cy.findByRole('button', { name: 'Unpublish' }).click() - cy.findByRole('button', { name: 'Publish' }) - cy.visit('/templates/all') - cy.findByRole('link', { name }).should('not.exist') - - // check for template links, after creating the first project - cy.visit('/') - cy.findAllByRole('button', { name: NEW_PROJECT_BUTTON_MATCHER }).click() - cy.findByRole('menuitem', { name: /All Templates/ }).should( - 'have.attr', - 'href', - '/templates/all' - ) - }) - }) - - function checkDisabled() { - it('should not have templates feature', function () { - login(TEMPLATES_USER) - - cy.visit('/') - createProject('maybe templates') - - cy.findByRole('navigation', { - name: 'Project actions', - }) - .findByRole('button', { name: 'Menu' }) - .click() - cy.findByRole('button', { name: 'Word Count' }) // wait for lazy loading - cy.findByRole('button', { name: 'Manage Template' }).should('not.exist') - - cy.visit('/templates', { failOnStatusCode: false }) - cy.findByRole('heading', { name: 'Not found' }) - cy.visit('/templates/all', { failOnStatusCode: false }) - cy.findByRole('heading', { name: 'Not found' }) - - // check for template links, after creating the first project - cy.visit('/') - cy.findAllByRole('button', { name: NEW_PROJECT_BUTTON_MATCHER }).click() - cy.findByRole('menuitem', { name: /All Templates/ }).should('not.exist') - }) - - it('should not show templates link on welcome page', function () { - login(WITHOUT_PROJECTS_USER) - cy.visit('/') - cy.findByRole('button', { name: NEW_PROJECT_BUTTON_MATCHER }) // wait for lazy loading - cy.findByRole('link', { name: LABEL_BROWSE_TEMPLATES }).should( - 'not.exist' - ) - }) - } - - describe('disabled Server Pro', function () { - if (isExcludedBySharding('PRO_DEFAULT_2')) return - startWith({ pro: true }) - checkDisabled() - }) - - describe('unavailable in CE', function () { - if (isExcludedBySharding('CE_CUSTOM_1')) return - startWith({ - pro: false, - varsFn, - }) - checkDisabled() - }) -}) diff --git a/services/web/frontend/js/features/ide-redesign/utils/new-editor-utils.ts b/services/web/frontend/js/features/ide-redesign/utils/new-editor-utils.ts index e442e33a25..40b3804068 100644 --- a/services/web/frontend/js/features/ide-redesign/utils/new-editor-utils.ts +++ b/services/web/frontend/js/features/ide-redesign/utils/new-editor-utils.ts @@ -1,12 +1,7 @@ import { useUserSettingsContext } from '@/shared/context/user-settings-context' -// For e2e tests purposes, allow overriding to old editor -export const oldEditorOverride = - new URLSearchParams(window.location.search).get('old-editor-override') === - 'true' - export const canUseNewEditor = () => { - return !oldEditorOverride + return true } export const useIsNewEditorEnabled = () => {