mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-11-12 13:13:45 +01:00
[saas-e2e] harden recompiling (#28514)
* [saas-e2e] harden recompiling * [server-ce] import changes from SaaS e2e tests GitOrigin-RevId: 9b86ed864457b068b8dded94a7e12658a047aac2
This commit is contained in:
@@ -4,7 +4,7 @@ import {
|
|||||||
openProjectViaInviteNotification,
|
openProjectViaInviteNotification,
|
||||||
} from './helpers/project'
|
} from './helpers/project'
|
||||||
import { isExcludedBySharding, startWith } from './helpers/config'
|
import { isExcludedBySharding, startWith } from './helpers/config'
|
||||||
import { throttledRecompile } from './helpers/compile'
|
import { prepareWaitForNextCompileSlot } from './helpers/compile'
|
||||||
|
|
||||||
const USER = 'user@example.com'
|
const USER = 'user@example.com'
|
||||||
const COLLABORATOR = 'collaborator@example.com'
|
const COLLABORATOR = 'collaborator@example.com'
|
||||||
@@ -17,8 +17,10 @@ describe('Project creation and compilation', function () {
|
|||||||
|
|
||||||
it('users can create project and compile it', function () {
|
it('users can create project and compile it', function () {
|
||||||
login(USER)
|
login(USER)
|
||||||
createProject('test-project')
|
const { recompile, waitForCompile } = prepareWaitForNextCompileSlot()
|
||||||
const recompile = throttledRecompile()
|
waitForCompile(() => {
|
||||||
|
createProject('test-project')
|
||||||
|
})
|
||||||
cy.findByRole('textbox', { name: 'Source Editor editing' }).within(() => {
|
cy.findByRole('textbox', { name: 'Source Editor editing' }).within(() => {
|
||||||
cy.findByText('\\maketitle').parent().click()
|
cy.findByText('\\maketitle').parent().click()
|
||||||
cy.findByText('\\maketitle').parent().type('\n\\section{{}Test Section}')
|
cy.findByText('\\maketitle').parent().type('\n\\section{{}Test Section}')
|
||||||
@@ -109,7 +111,6 @@ describe('Project creation and compilation', function () {
|
|||||||
cy.findByRole('navigation', { name: 'Project files and outline' })
|
cy.findByRole('navigation', { name: 'Project files and outline' })
|
||||||
.findByRole('button', { name: 'New file' })
|
.findByRole('button', { name: 'New file' })
|
||||||
.click()
|
.click()
|
||||||
|
|
||||||
cy.findByRole('dialog').within(() => {
|
cy.findByRole('dialog').within(() => {
|
||||||
cy.findByRole('button', { name: 'From another project' }).click()
|
cy.findByRole('button', { name: 'From another project' }).click()
|
||||||
cy.findByLabelText('Select a Project').select(sourceProjectName)
|
cy.findByLabelText('Select a Project').select(sourceProjectName)
|
||||||
|
|||||||
@@ -22,20 +22,19 @@ describe('editor', () => {
|
|||||||
let projectName: string
|
let projectName: string
|
||||||
let projectId: string
|
let projectId: string
|
||||||
let recompile: () => void
|
let recompile: () => void
|
||||||
let waitForCompileRateLimitCoolOff: (fn: () => void) => void
|
let waitForCompile: (fn: () => void) => void
|
||||||
beforeWithReRunOnTestRetry(function () {
|
beforeWithReRunOnTestRetry(function () {
|
||||||
projectName = `project-${uuid()}`
|
projectName = `project-${uuid()}`
|
||||||
login(USER)
|
login(USER)
|
||||||
createProject(projectName, { type: 'Example project', open: false }).then(
|
createProject(projectName, { type: 'Example project', open: false }).then(
|
||||||
id => (projectId = id)
|
id => (projectId = id)
|
||||||
)
|
)
|
||||||
;({ recompile, waitForCompileRateLimitCoolOff } =
|
;({ recompile, waitForCompile } = prepareWaitForNextCompileSlot())
|
||||||
prepareWaitForNextCompileSlot())
|
|
||||||
})
|
})
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
login(USER)
|
login(USER)
|
||||||
waitForCompileRateLimitCoolOff(() => {
|
waitForCompile(() => {
|
||||||
openProjectById(projectId)
|
openProjectById(projectId)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ describe('filestore migration', function () {
|
|||||||
const projectName = `project-${uuid()}`
|
const projectName = `project-${uuid()}`
|
||||||
let defaultImage: string
|
let defaultImage: string
|
||||||
let projectId: string
|
let projectId: string
|
||||||
let waitForCompileRateLimitCoolOff: (fn: () => void) => void
|
let waitForCompile: (fn: () => void) => void
|
||||||
const previousBinaryFiles: (() => void)[] = []
|
const previousBinaryFiles: (() => void)[] = []
|
||||||
|
|
||||||
function avoid502() {
|
function avoid502() {
|
||||||
@@ -55,7 +55,7 @@ describe('filestore migration', function () {
|
|||||||
) {
|
) {
|
||||||
before(function () {
|
before(function () {
|
||||||
login(email)
|
login(email)
|
||||||
waitForCompileRateLimitCoolOff(() => {
|
waitForCompile(() => {
|
||||||
cy.visit(`/project/${projectId}`)
|
cy.visit(`/project/${projectId}`)
|
||||||
})
|
})
|
||||||
previousBinaryFiles.push(prepareFileUploadTest(true))
|
previousBinaryFiles.push(prepareFileUploadTest(true))
|
||||||
@@ -112,8 +112,7 @@ describe('filestore migration', function () {
|
|||||||
.should('match', /\/project\/[a-fA-F0-9]{24}/)
|
.should('match', /\/project\/[a-fA-F0-9]{24}/)
|
||||||
.then(url => (projectId = url.split('/').pop()!))
|
.then(url => (projectId = url.split('/').pop()!))
|
||||||
let queueReset
|
let queueReset
|
||||||
;({ waitForCompileRateLimitCoolOff, queueReset } =
|
;({ waitForCompile, queueReset } = prepareWaitForNextCompileSlot())
|
||||||
prepareWaitForNextCompileSlot())
|
|
||||||
queueReset()
|
queueReset()
|
||||||
|
|
||||||
// Create a new binary file
|
// Create a new binary file
|
||||||
@@ -265,7 +264,7 @@ describe('filestore migration', function () {
|
|||||||
createProject(projectName, { type: 'Example project', open: false }).then(
|
createProject(projectName, { type: 'Example project', open: false }).then(
|
||||||
id => (projectId = id)
|
id => (projectId = id)
|
||||||
)
|
)
|
||||||
;({ waitForCompileRateLimitCoolOff } = prepareWaitForNextCompileSlot())
|
;({ waitForCompile } = prepareWaitForNextCompileSlot())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
addNewBinaryFileAndCheckPrevious()
|
addNewBinaryFileAndCheckPrevious()
|
||||||
@@ -298,7 +297,7 @@ describe('filestore migration', function () {
|
|||||||
// filestore-migration
|
// filestore-migration
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
login(email)
|
login(email)
|
||||||
waitForCompileRateLimitCoolOff(() => {
|
waitForCompile(() => {
|
||||||
openProjectById(projectId)
|
openProjectById(projectId)
|
||||||
})
|
})
|
||||||
ensureStopOnFirstErrorIsActive()
|
ensureStopOnFirstErrorIsActive()
|
||||||
@@ -327,7 +326,7 @@ describe('filestore migration', function () {
|
|||||||
.parent()
|
.parent()
|
||||||
.type(`\n\\section{{}Test Section ${id}}`)
|
.type(`\n\\section{{}Test Section ${id}}`)
|
||||||
|
|
||||||
waitForCompileRateLimitCoolOff(() => {
|
waitForCompile(() => {
|
||||||
cy.findByRole('button', { name: 'Toggle compile options menu' }).click()
|
cy.findByRole('button', { name: 'Toggle compile options menu' }).click()
|
||||||
|
|
||||||
cy.findByRole('menuitem', {
|
cy.findByRole('menuitem', {
|
||||||
|
|||||||
+211
-179
@@ -12,7 +12,7 @@ import {
|
|||||||
import git from 'isomorphic-git'
|
import git from 'isomorphic-git'
|
||||||
import http from 'isomorphic-git/http/web'
|
import http from 'isomorphic-git/http/web'
|
||||||
import LightningFS from '@isomorphic-git/lightning-fs'
|
import LightningFS from '@isomorphic-git/lightning-fs'
|
||||||
import { throttledRecompile } from './helpers/compile'
|
import { prepareWaitForNextCompileSlot } from './helpers/compile'
|
||||||
|
|
||||||
const USER = 'user@example.com'
|
const USER = 'user@example.com'
|
||||||
|
|
||||||
@@ -52,13 +52,13 @@ describe('git-bridge', function () {
|
|||||||
function maybeClearAllTokens() {
|
function maybeClearAllTokens() {
|
||||||
cy.visit('/user/settings')
|
cy.visit('/user/settings')
|
||||||
cy.findByRole('heading', { name: 'Git integration' })
|
cy.findByRole('heading', { name: 'Git integration' })
|
||||||
cy.get('button')
|
cy.findByRole('button', {
|
||||||
.contains(/Generate token|Add another token/)
|
name: /Generate token|Add another token/i,
|
||||||
.then(btn => {
|
}).then(btn => {
|
||||||
if (btn.text() === 'Add another token') {
|
if (btn.text() === 'Add another token') {
|
||||||
clearAllTokens()
|
clearAllTokens()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
@@ -130,9 +130,12 @@ describe('git-bridge', function () {
|
|||||||
cy.findByTestId('left-menu').within(() => {
|
cy.findByTestId('left-menu').within(() => {
|
||||||
cy.findByRole('button', { name: 'Git' }).click()
|
cy.findByRole('button', { name: 'Git' }).click()
|
||||||
})
|
})
|
||||||
|
|
||||||
cy.findByTestId('git-bridge-modal').within(() => {
|
cy.findByTestId('git-bridge-modal').within(() => {
|
||||||
cy.get('@projectId').then(id => {
|
cy.get('@projectId').then(id => {
|
||||||
cy.get('code').contains(`git clone ${gitURL(id.toString())}`)
|
cy.findByLabelText('Git clone project command').contains(
|
||||||
|
`git clone ${gitURL(id.toString())}`
|
||||||
|
)
|
||||||
})
|
})
|
||||||
cy.findByRole('button', {
|
cy.findByRole('button', {
|
||||||
name: 'Generate token',
|
name: 'Generate token',
|
||||||
@@ -151,14 +154,19 @@ describe('git-bridge', function () {
|
|||||||
ensureUserExists({ email: 'collaborator-link-ro@example.com' })
|
ensureUserExists({ email: 'collaborator-link-ro@example.com' })
|
||||||
|
|
||||||
let projectName: string
|
let projectName: string
|
||||||
|
let recompile: () => void
|
||||||
|
let waitForCompile: (triggerCompile: () => void) => void
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
projectName = uuid()
|
projectName = uuid()
|
||||||
createProject(projectName, { open: false }).as('projectId')
|
createProject(projectName, { open: false }).as('projectId')
|
||||||
|
;({ recompile, waitForCompile } = prepareWaitForNextCompileSlot())
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should expose r/w interface to owner', () => {
|
it('should expose r/w interface to owner', () => {
|
||||||
maybeClearAllTokens()
|
maybeClearAllTokens()
|
||||||
openProjectByName(projectName)
|
waitForCompile(() => {
|
||||||
|
openProjectByName(projectName)
|
||||||
|
})
|
||||||
checkGitAccess('readAndWrite')
|
checkGitAccess('readAndWrite')
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -169,7 +177,9 @@ describe('git-bridge', function () {
|
|||||||
'Editor'
|
'Editor'
|
||||||
)
|
)
|
||||||
maybeClearAllTokens()
|
maybeClearAllTokens()
|
||||||
openProjectByName(projectName)
|
waitForCompile(() => {
|
||||||
|
openProjectByName(projectName)
|
||||||
|
})
|
||||||
checkGitAccess('readAndWrite')
|
checkGitAccess('readAndWrite')
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -180,7 +190,9 @@ describe('git-bridge', function () {
|
|||||||
'Viewer'
|
'Viewer'
|
||||||
)
|
)
|
||||||
maybeClearAllTokens()
|
maybeClearAllTokens()
|
||||||
openProjectByName(projectName)
|
waitForCompile(() => {
|
||||||
|
openProjectByName(projectName)
|
||||||
|
})
|
||||||
checkGitAccess('readOnly')
|
checkGitAccess('readOnly')
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -190,212 +202,232 @@ describe('git-bridge', function () {
|
|||||||
const email = 'collaborator-link-rw@example.com'
|
const email = 'collaborator-link-rw@example.com'
|
||||||
login(email)
|
login(email)
|
||||||
maybeClearAllTokens()
|
maybeClearAllTokens()
|
||||||
openProjectViaLinkSharingAsUser(
|
waitForCompile(() => {
|
||||||
linkSharingReadAndWrite,
|
openProjectViaLinkSharingAsUser(
|
||||||
projectName,
|
linkSharingReadAndWrite,
|
||||||
email
|
projectName,
|
||||||
)
|
email
|
||||||
|
)
|
||||||
|
})
|
||||||
checkGitAccess('readAndWrite')
|
checkGitAccess('readAndWrite')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should expose r/o interface to link-sharing r/o collaborator', () => {
|
it('should expose r/o interface to link-sharing r/o collaborator', () => {
|
||||||
openProjectByName(projectName)
|
waitForCompile(() => {
|
||||||
|
openProjectByName(projectName)
|
||||||
|
})
|
||||||
enableLinkSharing().then(({ linkSharingReadOnly }) => {
|
enableLinkSharing().then(({ linkSharingReadOnly }) => {
|
||||||
const email = 'collaborator-link-ro@example.com'
|
const email = 'collaborator-link-ro@example.com'
|
||||||
login(email)
|
login(email)
|
||||||
maybeClearAllTokens()
|
maybeClearAllTokens()
|
||||||
openProjectViaLinkSharingAsUser(
|
waitForCompile(() => {
|
||||||
linkSharingReadOnly,
|
openProjectViaLinkSharingAsUser(
|
||||||
projectName,
|
linkSharingReadOnly,
|
||||||
email
|
projectName,
|
||||||
)
|
email
|
||||||
|
)
|
||||||
|
})
|
||||||
checkGitAccess('readOnly')
|
checkGitAccess('readOnly')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
|
||||||
|
|
||||||
function checkGitAccess(access: 'readOnly' | 'readAndWrite') {
|
function checkGitAccess(access: 'readOnly' | 'readAndWrite') {
|
||||||
const recompile = throttledRecompile()
|
cy.findByRole('navigation', {
|
||||||
|
name: 'Project actions',
|
||||||
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' })
|
.findByRole('button', { name: 'Menu' })
|
||||||
cy.findByRole('button', {
|
.click()
|
||||||
name: 'Generate token',
|
cy.findByTestId('left-menu').within(() => {
|
||||||
}).click()
|
cy.findByRole('heading', { name: 'Sync' })
|
||||||
cy.get('code')
|
cy.findByRole('button', { name: 'Git' }).click()
|
||||||
.contains(/olp_[a-zA-Z0-9]{16}/)
|
})
|
||||||
.then(async tokenEl => {
|
cy.get('@projectId').then(projectId => {
|
||||||
const token = tokenEl.text()
|
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
|
// close Git modal
|
||||||
cy.findAllByText('Close').last().click()
|
cy.get('body').type('{esc}')
|
||||||
// close editor menu
|
cy.findByTestId('git-bridge-modal').should('not.exist')
|
||||||
cy.get('.left-menu-modal-backdrop').click()
|
// close the modal
|
||||||
|
cy.get('body').type('{esc}')
|
||||||
|
cy.findByTestId('left-menu').should('not.exist')
|
||||||
|
const fs = new LightningFS('fs')
|
||||||
|
const dir = `/${projectId}`
|
||||||
|
|
||||||
const fs = new LightningFS('fs')
|
async function readFile(path: string): Promise<string> {
|
||||||
const dir = `/${projectId}`
|
return new Promise((resolve, reject) => {
|
||||||
|
fs.readFile(path, { encoding: 'utf8' }, (err, blob) => {
|
||||||
|
if (err) return reject(err)
|
||||||
|
resolve(blob as string)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
async function readFile(path: string): Promise<string> {
|
async function writeFile(path: string, data: string) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise<void>((resolve, reject) => {
|
||||||
fs.readFile(path, { encoding: 'utf8' }, (err, blob) => {
|
fs.writeFile(path, data, undefined, err => {
|
||||||
if (err) return reject(err)
|
if (err) return reject(err)
|
||||||
resolve(blob as string)
|
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()
|
||||||
async function writeFile(path: string, data: string) {
|
.parent()
|
||||||
return new Promise<void>((resolve, reject) => {
|
.then(async editor => {
|
||||||
fs.writeFile(path, data, undefined, err => {
|
const onDisk = await readFile(mainTex)
|
||||||
if (err) return reject(err)
|
expect(onDisk.replaceAll('\n', '')).to.equal(editor.text())
|
||||||
resolve()
|
|
||||||
})
|
})
|
||||||
})
|
const text = `
|
||||||
}
|
|
||||||
|
|
||||||
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}
|
\\documentclass{article}
|
||||||
\\begin{document}
|
\\begin{document}
|
||||||
Hello world
|
Hello world
|
||||||
\\end{document}
|
\\end{document}
|
||||||
`
|
`
|
||||||
|
|
||||||
// Make a change
|
// 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.findAllByText('History').last().click()
|
|
||||||
cy.findByText('(via Git)').should('not.exist')
|
|
||||||
cy.findAllByText('Back to editor').last().click()
|
|
||||||
|
|
||||||
cy.then(async () => {
|
cy.then(async () => {
|
||||||
await git.push({
|
await writeFile(mainTex, text)
|
||||||
|
await git.add({
|
||||||
...commonOptions,
|
...commonOptions,
|
||||||
...httpOptions,
|
filepath: 'main.tex',
|
||||||
|
})
|
||||||
|
await git.commit({
|
||||||
|
...commonOptions,
|
||||||
|
...authorOptions,
|
||||||
|
message: 'Swap main.tex',
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
} else {
|
|
||||||
cy.then(async () => {
|
if (access === 'readAndWrite') {
|
||||||
try {
|
// check history before push
|
||||||
|
cy.findByRole('navigation', {
|
||||||
|
name: 'Project actions',
|
||||||
|
})
|
||||||
|
.findByRole('button', { name: 'History' })
|
||||||
|
.click()
|
||||||
|
cy.findByText('(via Git)').should('not.exist')
|
||||||
|
cy.findAllByText('Back to editor').last().click()
|
||||||
|
cy.then(async () => {
|
||||||
await git.push({
|
await git.push({
|
||||||
...commonOptions,
|
...commonOptions,
|
||||||
...httpOptions,
|
...httpOptions,
|
||||||
})
|
})
|
||||||
expect.fail('push should have failed')
|
})
|
||||||
} catch (err) {
|
} else {
|
||||||
expect(err).to.match(/branches were not updated/)
|
cy.then(async () => {
|
||||||
expect(err).to.match(/forbidden/)
|
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',
|
||||||
})
|
})
|
||||||
|
.findByRole('button', { name: 'History' })
|
||||||
|
.click()
|
||||||
|
cy.findAllByText('Back to editor').last().click()
|
||||||
|
|
||||||
return // return early, below are write access bits
|
// check push in history
|
||||||
}
|
cy.findByRole('navigation', {
|
||||||
|
name: 'Project actions',
|
||||||
// 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.findAllByText('History').last().click()
|
|
||||||
cy.findAllByText('Back to editor').last().click()
|
|
||||||
|
|
||||||
// check push in history
|
|
||||||
cy.findAllByText('History').last().click()
|
|
||||||
cy.findByText(/Hello world/)
|
|
||||||
cy.findByText('(via Git)').should('exist')
|
|
||||||
|
|
||||||
// Back to the editor
|
|
||||||
cy.findAllByText('Back to editor').last().click()
|
|
||||||
cy.findByText(/\\documentclass/)
|
|
||||||
.parent()
|
|
||||||
.parent()
|
|
||||||
.click()
|
|
||||||
.type('% via editor{enter}')
|
|
||||||
|
|
||||||
// Trigger flush via compile
|
|
||||||
recompile()
|
|
||||||
|
|
||||||
// Back into the history, check what we just added
|
|
||||||
cy.findAllByText('History').last().click()
|
|
||||||
cy.findByText(/% via editor/)
|
|
||||||
|
|
||||||
// Pull the change
|
|
||||||
cy.then(async () => {
|
|
||||||
await git.pull({
|
|
||||||
...commonOptions,
|
|
||||||
...httpOptions,
|
|
||||||
...authorOptions,
|
|
||||||
})
|
})
|
||||||
|
.findByRole('button', { name: 'History' })
|
||||||
|
.click()
|
||||||
|
cy.findByText(/Hello world/)
|
||||||
|
cy.findByText('(via Git)').should('exist')
|
||||||
|
|
||||||
expect(await readFile(mainTex)).to.equal(text + '% via editor\n')
|
// Back to the editor
|
||||||
|
cy.findAllByText('Back to editor').last().click()
|
||||||
|
cy.findByText(/\\documentclass/)
|
||||||
|
.parent()
|
||||||
|
.parent()
|
||||||
|
.click()
|
||||||
|
.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() {
|
function checkDisabled() {
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import {
|
|||||||
} from './helpers/config'
|
} from './helpers/config'
|
||||||
import { dockerCompose, getRedisKeys } from './helpers/hostAdminClient'
|
import { dockerCompose, getRedisKeys } from './helpers/hostAdminClient'
|
||||||
import { createProject } from './helpers/project'
|
import { createProject } from './helpers/project'
|
||||||
import { throttledRecompile } from './helpers/compile'
|
import { prepareWaitForNextCompileSlot } from './helpers/compile'
|
||||||
|
|
||||||
const USER = 'user@example.com'
|
const USER = 'user@example.com'
|
||||||
const PROJECT_NAME = 'Old Project'
|
const PROJECT_NAME = 'Old Project'
|
||||||
@@ -31,10 +31,12 @@ describe('GracefulShutdown', function () {
|
|||||||
it('should display banner and flush changes out of redis', () => {
|
it('should display banner and flush changes out of redis', () => {
|
||||||
bringServerProBackUp()
|
bringServerProBackUp()
|
||||||
login(USER)
|
login(USER)
|
||||||
createProject(PROJECT_NAME).then(id => {
|
const { recompile, waitForCompile } = prepareWaitForNextCompileSlot()
|
||||||
projectId = id
|
waitForCompile(() => {
|
||||||
|
createProject(PROJECT_NAME).then(id => {
|
||||||
|
projectId = id
|
||||||
|
})
|
||||||
})
|
})
|
||||||
const recompile = throttledRecompile()
|
|
||||||
|
|
||||||
cy.log('add additional content')
|
cy.log('add additional content')
|
||||||
cy.findByText('\\maketitle').parent().click()
|
cy.findByText('\\maketitle').parent().click()
|
||||||
|
|||||||
@@ -3,11 +3,6 @@
|
|||||||
* The naive approach is waiting a fixed a mount of time (3s) just before clicking the button.
|
* The naive approach is waiting a fixed a mount of time (3s) just before clicking the button.
|
||||||
* This helper takes into account that other UI interactions take time. We can deduce that latency from the fixed delay (3s minus other latency). This can bring down the effective waiting time to 0s.
|
* This helper takes into account that other UI interactions take time. We can deduce that latency from the fixed delay (3s minus other latency). This can bring down the effective waiting time to 0s.
|
||||||
*/
|
*/
|
||||||
export function throttledRecompile() {
|
|
||||||
const { queueReset, recompile } = prepareWaitForNextCompileSlot()
|
|
||||||
queueReset()
|
|
||||||
return recompile
|
|
||||||
}
|
|
||||||
|
|
||||||
export function stopCompile(options: { delay?: number } = {}) {
|
export function stopCompile(options: { delay?: number } = {}) {
|
||||||
const { delay = 0 } = options
|
const { delay = 0 } = options
|
||||||
@@ -24,25 +19,51 @@ export function prepareWaitForNextCompileSlot() {
|
|||||||
lastCompile = Date.now()
|
lastCompile = Date.now()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
function waitForCompileRateLimitCoolOff(triggerCompile: () => void) {
|
function waitForCompileRateLimitCoolOff() {
|
||||||
cy.then(() => {
|
cy.then(() => {
|
||||||
cy.log('Wait for recompile rate-limit to cool off')
|
cy.log('Wait for recompile rate-limit to cool off')
|
||||||
const msSinceLastCompile = Date.now() - lastCompile
|
const msSinceLastCompile = Date.now() - lastCompile
|
||||||
cy.wait(Math.max(0, 1_000 - msSinceLastCompile))
|
cy.wait(Math.max(0, 1_000 - msSinceLastCompile))
|
||||||
queueReset()
|
queueReset()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
function waitForCompile(triggerCompile: () => void) {
|
||||||
|
waitForCompileRateLimitCoolOff()
|
||||||
|
cy.then(() => {
|
||||||
|
let compilingVisible: () => void
|
||||||
|
const waitForCompilingVisible = new Promise<void>(resolve => {
|
||||||
|
compilingVisible = resolve
|
||||||
|
})
|
||||||
|
cy.intercept(
|
||||||
|
{
|
||||||
|
method: 'POST',
|
||||||
|
pathname: /\/project\/[a-fA-F0-9]{24}\/compile$/,
|
||||||
|
times: 1,
|
||||||
|
},
|
||||||
|
async req => {
|
||||||
|
await waitForCompilingVisible
|
||||||
|
req.continue()
|
||||||
|
}
|
||||||
|
).as('recompile')
|
||||||
triggerCompile()
|
triggerCompile()
|
||||||
cy.log('Wait for compile to finish')
|
cy.log('Wait for compile to finish')
|
||||||
|
cy.findByRole('button', { name: 'Compiling…' }).then(() =>
|
||||||
|
compilingVisible()
|
||||||
|
)
|
||||||
|
cy.wait('@recompile')
|
||||||
|
cy.findByRole('button', { name: 'Compiling…' }).should('not.exist')
|
||||||
cy.findByRole('button', { name: 'Recompile' }).should('be.visible')
|
cy.findByRole('button', { name: 'Recompile' }).should('be.visible')
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
function recompile() {
|
function recompile() {
|
||||||
waitForCompileRateLimitCoolOff(() => {
|
waitForCompile(() => {
|
||||||
cy.findByText('Recompile').click()
|
cy.findByRole('button', { name: 'Recompile' }).click()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
queueReset,
|
queueReset,
|
||||||
waitForCompileRateLimitCoolOff,
|
waitForCompileRateLimitCoolOff,
|
||||||
|
waitForCompile,
|
||||||
recompile,
|
recompile,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { createProject } from './helpers/project'
|
import { createProject } from './helpers/project'
|
||||||
import { throttledRecompile } from './helpers/compile'
|
import { prepareWaitForNextCompileSlot } from './helpers/compile'
|
||||||
import { ensureUserExists, login } from './helpers/login'
|
import { ensureUserExists, login } from './helpers/login'
|
||||||
import { isExcludedBySharding, startWith } from './helpers/config'
|
import { isExcludedBySharding, startWith } from './helpers/config'
|
||||||
|
|
||||||
@@ -62,8 +62,10 @@ describe('History', function () {
|
|||||||
const CLASS_DELETION = 'ol-cm-deletion-marker'
|
const CLASS_DELETION = 'ol-cm-deletion-marker'
|
||||||
|
|
||||||
it('should support labels, comparison and download', () => {
|
it('should support labels, comparison and download', () => {
|
||||||
createProject('labels')
|
const { recompile, waitForCompile } = prepareWaitForNextCompileSlot()
|
||||||
const recompile = throttledRecompile()
|
waitForCompile(() => {
|
||||||
|
createProject('labels')
|
||||||
|
})
|
||||||
|
|
||||||
cy.log('add content, including a line that will get removed soon')
|
cy.log('add content, including a line that will get removed soon')
|
||||||
cy.findByRole('textbox', { name: 'Source Editor editing' }).within(() => {
|
cy.findByRole('textbox', { name: 'Source Editor editing' }).within(() => {
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ import {
|
|||||||
shareProjectByEmailAndAcceptInviteViaDash,
|
shareProjectByEmailAndAcceptInviteViaDash,
|
||||||
shareProjectByEmailAndAcceptInviteViaEmail,
|
shareProjectByEmailAndAcceptInviteViaEmail,
|
||||||
} from './helpers/project'
|
} from './helpers/project'
|
||||||
import { throttledRecompile } from './helpers/compile'
|
import { prepareWaitForNextCompileSlot } from './helpers/compile'
|
||||||
import { beforeWithReRunOnTestRetry } from './helpers/beforeWithReRunOnTestRetry'
|
import { beforeWithReRunOnTestRetry } from './helpers/beforeWithReRunOnTestRetry'
|
||||||
|
|
||||||
describe('Project Sharing', function () {
|
describe('Project Sharing', function () {
|
||||||
@@ -25,8 +25,11 @@ describe('Project Sharing', function () {
|
|||||||
startWith({ withDataDir: true, pro: true })
|
startWith({ withDataDir: true, pro: true })
|
||||||
|
|
||||||
let projectName: string
|
let projectName: string
|
||||||
|
let recompile: () => void
|
||||||
|
let waitForCompile: (triggerCompile: () => void) => void
|
||||||
beforeWithReRunOnTestRetry(function () {
|
beforeWithReRunOnTestRetry(function () {
|
||||||
projectName = getSpamSafeProjectName()
|
projectName = getSpamSafeProjectName()
|
||||||
|
;({ recompile, waitForCompile } = prepareWaitForNextCompileSlot())
|
||||||
setupTestProject()
|
setupTestProject()
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -40,7 +43,9 @@ describe('Project Sharing', function () {
|
|||||||
|
|
||||||
function setupTestProject() {
|
function setupTestProject() {
|
||||||
login('user@example.com')
|
login('user@example.com')
|
||||||
createProject(projectName)
|
waitForCompile(() => {
|
||||||
|
createProject(projectName)
|
||||||
|
})
|
||||||
|
|
||||||
// Add chat message
|
// Add chat message
|
||||||
cy.findByRole('button', { name: 'Chat' }).click()
|
cy.findByRole('button', { name: 'Chat' }).click()
|
||||||
@@ -75,7 +80,6 @@ describe('Project Sharing', function () {
|
|||||||
function expectContentWriteAccess() {
|
function expectContentWriteAccess() {
|
||||||
const section = `Test Section ${uuid()}`
|
const section = `Test Section ${uuid()}`
|
||||||
cy.url().should('match', /\/project\/[a-fA-F0-9]{24}/)
|
cy.url().should('match', /\/project\/[a-fA-F0-9]{24}/)
|
||||||
const recompile = throttledRecompile()
|
|
||||||
// wait for the editor to finish loading
|
// wait for the editor to finish loading
|
||||||
cy.findByRole('textbox', { name: 'Source Editor editing' }).should(
|
cy.findByRole('textbox', { name: 'Source Editor editing' }).should(
|
||||||
'contain.text',
|
'contain.text',
|
||||||
@@ -87,8 +91,10 @@ describe('Project Sharing', function () {
|
|||||||
'contenteditable',
|
'contenteditable',
|
||||||
'true'
|
'true'
|
||||||
)
|
)
|
||||||
cy.findByText('\\maketitle').parent().click()
|
cy.findByRole('textbox', { name: 'Source Editor editing' }).within(() => {
|
||||||
cy.findByText('\\maketitle').parent().type(`\n\\section{{}${section}}`)
|
cy.findByText('\\maketitle').parent().click()
|
||||||
|
cy.findByText('\\maketitle').parent().type(`\n\\section{{}${section}}`)
|
||||||
|
})
|
||||||
// should have written
|
// should have written
|
||||||
cy.findByRole('textbox', { name: 'Source Editor editing' }).should(
|
cy.findByRole('textbox', { name: 'Source Editor editing' }).should(
|
||||||
'contain.text',
|
'contain.text',
|
||||||
@@ -128,13 +134,28 @@ describe('Project Sharing', function () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function expectHistoryAccess() {
|
function expectHistoryAccess() {
|
||||||
cy.findByText('History').click()
|
cy.findByRole('button', { name: 'History' }).click()
|
||||||
cy.findByText('Labels')
|
// 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.findByText(/\\begin\{document}/)
|
||||||
cy.findAllByTestId('history-version-metadata-users')
|
cy.findByRole('complementary', {
|
||||||
.last()
|
name: 'Project history and labels',
|
||||||
.should('have.text', 'user')
|
}).within(() => {
|
||||||
cy.findByText('Back to editor').click()
|
cy.findAllByTestId('history-version-metadata-users')
|
||||||
|
.last()
|
||||||
|
.should('have.text', 'user')
|
||||||
|
})
|
||||||
|
cy.findByRole('button', { name: 'Back to editor' }).click()
|
||||||
}
|
}
|
||||||
|
|
||||||
function expectNoChatAccess() {
|
function expectNoChatAccess() {
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
import { ensureUserExists, login } from './helpers/login'
|
import { ensureUserExists, login } from './helpers/login'
|
||||||
import { createProject } from './helpers/project'
|
import { createProject } from './helpers/project'
|
||||||
import { isExcludedBySharding, startWith } from './helpers/config'
|
import { isExcludedBySharding, startWith } from './helpers/config'
|
||||||
import { throttledRecompile, stopCompile } from './helpers/compile'
|
import { prepareWaitForNextCompileSlot, stopCompile } from './helpers/compile'
|
||||||
import { v4 as uuid } from 'uuid'
|
import { v4 as uuid } from 'uuid'
|
||||||
import { waitUntilScrollingFinished } from './helpers/waitUntilScrollingFinished'
|
import { waitUntilScrollingFinished } from './helpers/waitUntilScrollingFinished'
|
||||||
import { beforeWithReRunOnTestRetry } from './helpers/beforeWithReRunOnTestRetry'
|
|
||||||
|
|
||||||
const LABEL_TEX_LIVE_VERSION = 'TeX Live version'
|
const LABEL_TEX_LIVE_VERSION = 'TeX Live version'
|
||||||
|
|
||||||
@@ -27,8 +26,10 @@ describe('SandboxedCompiles', function () {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should offer TexLive images and switch the compiler', function () {
|
it('should offer TexLive images and switch the compiler', function () {
|
||||||
createProject('sandboxed')
|
const { recompile, waitForCompile } = prepareWaitForNextCompileSlot()
|
||||||
const recompile = throttledRecompile()
|
waitForCompile(() => {
|
||||||
|
createProject('sandboxed')
|
||||||
|
})
|
||||||
cy.log('wait for compile')
|
cy.log('wait for compile')
|
||||||
cy.findByRole('region', { name: 'PDF preview and logs' }).should(
|
cy.findByRole('region', { name: 'PDF preview and logs' }).should(
|
||||||
'contain.text',
|
'contain.text',
|
||||||
@@ -53,7 +54,6 @@ describe('SandboxedCompiles', function () {
|
|||||||
})
|
})
|
||||||
cy.get('body').type('{esc}')
|
cy.get('body').type('{esc}')
|
||||||
cy.findByRole('dialog').should('not.exist')
|
cy.findByRole('dialog').should('not.exist')
|
||||||
|
|
||||||
cy.log('Trigger compile with other TeX Live version')
|
cy.log('Trigger compile with other TeX Live version')
|
||||||
recompile()
|
recompile()
|
||||||
|
|
||||||
@@ -71,13 +71,18 @@ describe('SandboxedCompiles', function () {
|
|||||||
function checkStopCompile() {
|
function checkStopCompile() {
|
||||||
it('users can stop a running compile', function () {
|
it('users can stop a running compile', function () {
|
||||||
login('user@example.com')
|
login('user@example.com')
|
||||||
createProject('test-project')
|
const { recompile, waitForCompile, waitForCompileRateLimitCoolOff } =
|
||||||
|
prepareWaitForNextCompileSlot()
|
||||||
|
waitForCompile(() => {
|
||||||
|
createProject('test-project')
|
||||||
|
})
|
||||||
// create an infinite loop in the main document
|
// create an infinite loop in the main document
|
||||||
// this will cause the compile to run indefinitely
|
// this will cause the compile to run indefinitely
|
||||||
cy.findByText('\\maketitle').parent().click()
|
cy.findByText('\\maketitle').parent().click()
|
||||||
cy.findByText('\\maketitle')
|
cy.findByText('\\maketitle')
|
||||||
.parent()
|
.parent()
|
||||||
.type('\n\\def\\x{{}Hello!\\par\\x}\\x')
|
.type('\n\\def\\x{{}Hello!\\par\\x}\\x')
|
||||||
|
waitForCompileRateLimitCoolOff()
|
||||||
cy.log('Start compile')
|
cy.log('Start compile')
|
||||||
// We need to start the compile manually because we do not want to wait for it to finish
|
// We need to start the compile manually because we do not want to wait for it to finish
|
||||||
cy.findByText('Recompile').click()
|
cy.findByText('Recompile').click()
|
||||||
@@ -90,7 +95,7 @@ describe('SandboxedCompiles', function () {
|
|||||||
// disabling the infinite loop and recompiling
|
// disabling the infinite loop and recompiling
|
||||||
cy.findByText('\\def').parent().click()
|
cy.findByText('\\def').parent().click()
|
||||||
cy.findByText('\\def').parent().type('{home}disabled loop% ')
|
cy.findByText('\\def').parent().type('{home}disabled loop% ')
|
||||||
cy.findByText('Recompile').click()
|
recompile()
|
||||||
cy.get('.pdf-viewer').should('contain.text', 'disabled loop')
|
cy.get('.pdf-viewer').should('contain.text', 'disabled loop')
|
||||||
cy.get('.logs-pane').should(
|
cy.get('.logs-pane').should(
|
||||||
'not.contain.text',
|
'not.contain.text',
|
||||||
@@ -104,8 +109,10 @@ describe('SandboxedCompiles', function () {
|
|||||||
let projectName: string
|
let projectName: string
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
projectName = `Project ${uuid()}`
|
projectName = `Project ${uuid()}`
|
||||||
createProject(projectName)
|
const { recompile, waitForCompile } = prepareWaitForNextCompileSlot()
|
||||||
const recompile = throttledRecompile()
|
waitForCompile(() => {
|
||||||
|
createProject(projectName)
|
||||||
|
})
|
||||||
cy.findByRole('textbox', { name: 'Source Editor editing' }).within(
|
cy.findByRole('textbox', { name: 'Source Editor editing' }).within(
|
||||||
() => {
|
() => {
|
||||||
cy.findByText('\\maketitle').parent().click()
|
cy.findByText('\\maketitle').parent().click()
|
||||||
@@ -205,8 +212,10 @@ describe('SandboxedCompiles', function () {
|
|||||||
function checkRecompilesAfterErrors() {
|
function checkRecompilesAfterErrors() {
|
||||||
it('recompiles even if there are Latex errors', function () {
|
it('recompiles even if there are Latex errors', function () {
|
||||||
login('user@example.com')
|
login('user@example.com')
|
||||||
createProject('test-project')
|
const { recompile, waitForCompile } = prepareWaitForNextCompileSlot()
|
||||||
const recompile = throttledRecompile()
|
waitForCompile(() => {
|
||||||
|
createProject('test-project')
|
||||||
|
})
|
||||||
cy.findByRole('textbox', { name: 'Source Editor editing' }).within(() => {
|
cy.findByRole('textbox', { name: 'Source Editor editing' }).within(() => {
|
||||||
cy.findByText('\\maketitle').parent().click()
|
cy.findByText('\\maketitle').parent().click()
|
||||||
cy.findByText('\\maketitle')
|
cy.findByText('\\maketitle')
|
||||||
@@ -224,8 +233,10 @@ describe('SandboxedCompiles', function () {
|
|||||||
|
|
||||||
function checkXeTeX() {
|
function checkXeTeX() {
|
||||||
it('should be able to use XeLaTeX', function () {
|
it('should be able to use XeLaTeX', function () {
|
||||||
createProject('XeLaTeX')
|
const { recompile, waitForCompile } = prepareWaitForNextCompileSlot()
|
||||||
const recompile = throttledRecompile()
|
waitForCompile(() => {
|
||||||
|
createProject('XeLaTeX')
|
||||||
|
})
|
||||||
cy.log('wait for compile')
|
cy.log('wait for compile')
|
||||||
cy.findByRole('region', { name: 'PDF preview and logs' }).should(
|
cy.findByRole('region', { name: 'PDF preview and logs' }).should(
|
||||||
'contain.text',
|
'contain.text',
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { ensureUserExists, login } from './helpers/login'
|
|||||||
import { isExcludedBySharding, startWith } from './helpers/config'
|
import { isExcludedBySharding, startWith } from './helpers/config'
|
||||||
import { dockerCompose, runScript } from './helpers/hostAdminClient'
|
import { dockerCompose, runScript } from './helpers/hostAdminClient'
|
||||||
import { createProject, openProjectByName } from './helpers/project'
|
import { createProject, openProjectByName } from './helpers/project'
|
||||||
import { throttledRecompile } from './helpers/compile'
|
import { prepareWaitForNextCompileSlot } from './helpers/compile'
|
||||||
import { v4 as uuid } from 'uuid'
|
import { v4 as uuid } from 'uuid'
|
||||||
|
|
||||||
const USER = 'user@example.com'
|
const USER = 'user@example.com'
|
||||||
@@ -11,6 +11,9 @@ const PROJECT_NAME = 'Old Project'
|
|||||||
describe('Upgrading', function () {
|
describe('Upgrading', function () {
|
||||||
if (isExcludedBySharding('PRO_CUSTOM_3')) return
|
if (isExcludedBySharding('PRO_CUSTOM_3')) return
|
||||||
|
|
||||||
|
let recompile: () => void
|
||||||
|
let waitForCompile: (triggerCompile: () => void) => void
|
||||||
|
|
||||||
function testUpgrade(
|
function testUpgrade(
|
||||||
steps: {
|
steps: {
|
||||||
version: string
|
version: string
|
||||||
@@ -38,10 +41,13 @@ describe('Upgrading', function () {
|
|||||||
before(() => {
|
before(() => {
|
||||||
cy.log('Populate old instance')
|
cy.log('Populate old instance')
|
||||||
login(USER)
|
login(USER)
|
||||||
createProject(PROJECT_NAME, {
|
;({ recompile, waitForCompile } = prepareWaitForNextCompileSlot())
|
||||||
newProjectButtonMatcher: startOptions.newProjectButtonMatcher,
|
waitForCompile(() => {
|
||||||
|
createProject(PROJECT_NAME, {
|
||||||
|
newProjectButtonMatcher: startOptions.newProjectButtonMatcher,
|
||||||
|
})
|
||||||
})
|
})
|
||||||
const recompile = throttledRecompile()
|
|
||||||
cy.log('Wait for successful compile')
|
cy.log('Wait for successful compile')
|
||||||
cy.get('.pdf-viewer').should('contain.text', PROJECT_NAME)
|
cy.get('.pdf-viewer').should('contain.text', PROJECT_NAME)
|
||||||
|
|
||||||
@@ -117,7 +123,9 @@ describe('Upgrading', function () {
|
|||||||
})
|
})
|
||||||
|
|
||||||
it('should open the old project', () => {
|
it('should open the old project', () => {
|
||||||
openProjectByName(PROJECT_NAME)
|
waitForCompile(() => {
|
||||||
|
openProjectByName(PROJECT_NAME)
|
||||||
|
})
|
||||||
|
|
||||||
cy.url().should('match', /\/project\/[a-fA-F0-9]{24}/)
|
cy.url().should('match', /\/project\/[a-fA-F0-9]{24}/)
|
||||||
cy.findByRole('navigation', {
|
cy.findByRole('navigation', {
|
||||||
@@ -125,7 +133,6 @@ describe('Upgrading', function () {
|
|||||||
}).within(() => {
|
}).within(() => {
|
||||||
cy.findByText(PROJECT_NAME)
|
cy.findByText(PROJECT_NAME)
|
||||||
})
|
})
|
||||||
const recompile = throttledRecompile()
|
|
||||||
|
|
||||||
cy.log('wait for successful compile')
|
cy.log('wait for successful compile')
|
||||||
cy.get('.pdf-viewer').should('contain.text', PROJECT_NAME)
|
cy.get('.pdf-viewer').should('contain.text', PROJECT_NAME)
|
||||||
@@ -189,9 +196,9 @@ describe('Upgrading', function () {
|
|||||||
hook() {
|
hook() {
|
||||||
before(function () {
|
before(function () {
|
||||||
login(USER)
|
login(USER)
|
||||||
cy.visit('/')
|
waitForCompile(() => {
|
||||||
cy.findByText(PROJECT_NAME).click()
|
openProjectByName(PROJECT_NAME)
|
||||||
const recompile = throttledRecompile()
|
})
|
||||||
|
|
||||||
cy.log('Make a change')
|
cy.log('Make a change')
|
||||||
cy.findByText('\\maketitle').parent().click()
|
cy.findByText('\\maketitle').parent().click()
|
||||||
|
|||||||
Reference in New Issue
Block a user