Merge pull request #18465 from overleaf/jpa-more-server-pro-e2e-tests

[server-pro] add more e2e tests for Server Pro

GitOrigin-RevId: 003a92ae6c12b58d1d31679f9d9e54d83cfc4a1e
This commit is contained in:
Jakob Ackermann 2024-05-30 09:13:24 +02:00 committed by Copybot
parent 8eb8b233c0
commit 2754c90ea6
14 changed files with 774 additions and 83 deletions

View file

@ -403,76 +403,6 @@ if (
} }
} }
// ######################
// Overleaf Server Pro
// ######################
if (parse(process.env.OVERLEAF_IS_SERVER_PRO) === true) {
settings.bypassPercentageRollouts = true
settings.apis.references = { url: 'http://127.0.0.1:3040' }
}
// Compiler
// --------
if (process.env.SANDBOXED_COMPILES === 'true') {
settings.clsi = {
dockerRunner: true,
docker: {
image: process.env.TEX_LIVE_DOCKER_IMAGE,
env: {
HOME: '/tmp',
PATH:
process.env.COMPILER_PATH ||
'/usr/local/texlive/2015/bin/x86_64-linux:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin',
},
user: 'www-data',
},
}
if (settings.path == null) {
settings.path = {}
}
settings.path.synctexBaseDir = () => '/compile'
if (process.env.SANDBOXED_COMPILES_SIBLING_CONTAINERS === 'true') {
console.log('Using sibling containers for sandboxed compiles')
if (process.env.SANDBOXED_COMPILES_HOST_DIR) {
settings.path.sandboxedCompilesHostDir =
process.env.SANDBOXED_COMPILES_HOST_DIR
} else {
console.error(
'Sibling containers, but SANDBOXED_COMPILES_HOST_DIR not set'
)
}
}
}
// Templates
// ---------
if (process.env.OVERLEAF_TEMPLATES_USER_ID) {
settings.templates = {
mountPointUrl: '/templates',
user_id: process.env.OVERLEAF_TEMPLATES_USER_ID,
}
settings.templateLinks = parse(
process.env.OVERLEAF_NEW_PROJECT_TEMPLATE_LINKS
)
}
// /Learn
// -------
if (process.env.OVERLEAF_PROXY_LEARN != null) {
settings.proxyLearn = parse(process.env.OVERLEAF_PROXY_LEARN)
if (settings.proxyLearn) {
settings.nav.header_extras = [
{
url: '/learn',
text: 'documentation',
},
].concat(settings.nav.header_extras || [])
}
}
// /References // /References
// ----------- // -----------
if (process.env.OVERLEAF_ELASTICSEARCH_URL != null) { if (process.env.OVERLEAF_ELASTICSEARCH_URL != null) {

View file

@ -6,6 +6,9 @@ all: test-e2e
# Notable the container labels com.docker.compose.project.working_dir and com.docker.compose.project.config_files need to match when creating containers from the docker host (how you started things) and from host-admin (how tests reconfigure the instance). # Notable the container labels com.docker.compose.project.working_dir and com.docker.compose.project.config_files need to match when creating containers from the docker host (how you started things) and from host-admin (how tests reconfigure the instance).
export PWD = $(shell pwd) export PWD = $(shell pwd)
export TEX_LIVE_DOCKER_IMAGE ?= quay.io/sharelatex/texlive-full:2022.1
export ALL_TEX_LIVE_DOCKER_IMAGES ?= quay.io/sharelatex/texlive-full:2022.1,quay.io/sharelatex/texlive-full:2021.1
test-e2e: test-e2e:
docker compose up --build --no-log-prefix --exit-code-from=e2e e2e docker compose up --build --no-log-prefix --exit-code-from=e2e e2e
@ -18,5 +21,7 @@ clean:
prefetch: prefetch:
docker compose pull e2e mongo redis docker compose pull e2e mongo redis
docker compose build docker compose build
echo -n "$$ALL_TEX_LIVE_DOCKER_IMAGES" | xargs -d, -I% \
sh -exc 'tag=%; re_tag=quay.io/sharelatex/$${tag#*/}; docker pull $$tag; docker tag $$tag $$re_tag'
.PHONY: test-e2e test-e2e-open .PHONY: test-e2e test-e2e-open

View file

@ -1,6 +1,7 @@
import { ensureUserExists, login } from './helpers/login' import { ensureUserExists, login } from './helpers/login'
import { createProject } from './helpers/project' import { createProject } from './helpers/project'
import { startWith } from './helpers/config' import { startWith } from './helpers/config'
import { throttledRecompile } from './helpers/compile'
describe('Project creation and compilation', function () { describe('Project creation and compilation', function () {
startWith({}) startWith({})
@ -13,12 +14,10 @@ describe('Project creation and compilation', function () {
// this is the first project created, the welcome screen is displayed instead of the project list // this is the first project created, the welcome screen is displayed instead of the project list
createProject('test-project') createProject('test-project')
cy.url().should('match', /\/project\/[a-fA-F0-9]{24}/) cy.url().should('match', /\/project\/[a-fA-F0-9]{24}/)
const recompile = throttledRecompile()
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}')
// Wait for the PDF compilation throttling recompile()
// eslint-disable-next-line cypress/no-unnecessary-waiting
cy.wait(3000)
cy.findByText('Recompile').click()
cy.get('.pdf-viewer').should('contain.text', 'Test Section') cy.get('.pdf-viewer').should('contain.text', 'Test Section')
}) })

View file

@ -1,2 +1,3 @@
downloads/ downloads/
results/ results/
compiles/

View file

@ -1 +1,7 @@
import '@testing-library/cypress/add-commands' import '@testing-library/cypress/add-commands'
Cypress.on('uncaught:exception', (err, runnable) => {
if (err.message.includes('ResizeObserver')) {
return false
}
})

View file

@ -14,6 +14,7 @@ services:
# The host-admin service initiates the mongo replica set # The host-admin service initiates the mongo replica set
condition: service_healthy condition: service_healthy
environment: environment:
OVERLEAF_SITE_URL: 'http://sharelatex'
OVERLEAF_APP_NAME: Overleaf Community Edition OVERLEAF_APP_NAME: Overleaf Community Edition
OVERLEAF_MONGO_URL: mongodb://mongo/sharelatex?directConnection=true OVERLEAF_MONGO_URL: mongodb://mongo/sharelatex?directConnection=true
OVERLEAF_REDIS_HOST: redis OVERLEAF_REDIS_HOST: redis
@ -39,6 +40,16 @@ services:
redis: redis:
image: redis:7.2.1 image: redis:7.2.1
git-bridge:
image: quay.io/sharelatex/git-bridge:latest
environment:
GIT_BRIDGE_API_BASE_URL: "http://sharelatex:3000/api/v0/" # "http://sharelatex/api/v0/" for version 4.1.6 and earlier
GIT_BRIDGE_OAUTH2_SERVER: "http://sharelatex"
GIT_BRIDGE_POSTBACK_BASE_URL: "http://git-bridge:8000"
GIT_BRIDGE_ROOT_DIR: "/data/git-bridge"
user: root
command: ["/server-pro-start.sh"]
e2e: e2e:
image: cypress/included:13.6.6 image: cypress/included:13.6.6
stop_grace_period: 0s stop_grace_period: 0s
@ -87,6 +98,8 @@ services:
stop_grace_period: 0s stop_grace_period: 0s
environment: environment:
PWD: PWD:
TEX_LIVE_DOCKER_IMAGE:
ALL_TEX_LIVE_DOCKER_IMAGES:
IMAGE_TAG_CE: ${IMAGE_TAG_CE:-sharelatex/sharelatex:latest} IMAGE_TAG_CE: ${IMAGE_TAG_CE:-sharelatex/sharelatex:latest}
IMAGE_TAG_PRO: ${IMAGE_TAG_PRO:-quay.io/sharelatex/sharelatex-pro:latest} IMAGE_TAG_PRO: ${IMAGE_TAG_PRO:-quay.io/sharelatex/sharelatex-pro:latest}
depends_on: depends_on:

View file

@ -0,0 +1,280 @@
import { startWith } from './helpers/config'
import { ensureUserExists, login } from './helpers/login'
import { createProject } from './helpers/project'
import git from 'isomorphic-git'
import http from 'isomorphic-git/http/web'
import LightningFS from '@isomorphic-git/lightning-fs'
import { throttledRecompile } from './helpers/compile'
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',
}
describe('enabled in Server Pro', function () {
startWith({
pro: true,
vars: ENABLED_VARS,
})
ensureUserExists({ email: 'user@example.com' })
function clearAllTokens() {
cy.get('button.linking-git-bridge-revoke-button').each(el => {
cy.wrap(el).click()
cy.findByText('Delete token').click()
})
}
function maybeClearAllTokens() {
cy.visit('/user/settings')
cy.findByText('Git Integration')
cy.get('button')
.contains(/Generate token|Add another token/)
.then(btn => {
if (btn.text() === 'Add another token') {
clearAllTokens()
}
})
}
beforeEach(function () {
login('user@example.com')
maybeClearAllTokens()
})
it('should render the git-bridge UI in the settings', () => {
cy.visit('/user/settings')
cy.findByText('Git Integration')
cy.get('button').contains('Generate token').click()
cy.get('code')
.contains(/olp_[a-zA-Z0-9]{16}/)
.as('newToken')
cy.findAllByText('Close').last().click()
cy.get('@newToken').then(token => {
// There can be more than one token with the same prefix when retrying
cy.findAllByText(
`${token.text().slice(0, 'olp_1234'.length)}${'*'.repeat(12)}`
).should('have.length.at.least', 1)
})
cy.get('button').contains('Generate token').should('not.exist')
cy.get('button').contains('Add another token').should('exist')
clearAllTokens()
cy.get('button').contains('Generate token').should('exist')
cy.get('button').contains('Add another token').should('not.exist')
})
it('should render the git-bridge UI in the editor', function () {
cy.visit('/project')
createProject('git').as('projectId')
cy.get('header').findByText('Menu').click()
cy.findByText('Sync')
cy.findByText('Git').click()
cy.findByRole('dialog').within(() => {
cy.get('@projectId').then(id => {
cy.get('code').contains(`git clone http://git@sharelatex/git/${id}`)
})
cy.findByRole('button', {
name: 'Generate token',
}).click()
cy.get('code').contains(/olp_[a-zA-Z0-9]{16}/)
})
// Re-open
cy.url().then(url => cy.visit(url))
cy.get('header').findByText('Menu').click()
cy.findByText('Git').click()
cy.findByRole('dialog').within(() => {
cy.get('@projectId').then(id => {
cy.get('code').contains(`git clone http://git@sharelatex/git/${id}`)
})
cy.findByText('Generate token').should('not.exist')
cy.findByText(/generate a new one in Account Settings/)
cy.findByText('Go to settings')
.should('have.attr', 'target', '_blank')
.and('have.attr', 'href', '/user/settings')
})
})
it('should expose interface for git', () => {
cy.visit('/project')
createProject('git').as('projectId')
const recompile = throttledRecompile()
cy.get('header').findByText('Menu').click()
cy.findByText('Sync')
cy.findByText('Git').click()
cy.get('@projectId').then(projectId => {
cy.findByRole('dialog').within(() => {
cy.get('code').contains(
`git clone http://git@sharelatex/git/${projectId}`
)
})
cy.findByRole('button', {
name: 'Generate token',
}).click()
cy.get('code')
.contains(/olp_[a-zA-Z0-9]{16}/)
.then(async tokenEl => {
const token = tokenEl.text()
// close Git modal
cy.findAllByText('Close').last().click()
// close editor menu
cy.get('#left-menu-modal').click()
// check history
cy.findAllByText('History').last().click()
cy.findByText('(via Git)').should('not.exist')
cy.findAllByText('Back to editor').last().click()
const fs = new LightningFS('fs')
const dir = `/${projectId}`
async function readFile(path: string) {
return new Promise((resolve, reject) => {
fs.readFile(path, { encoding: 'utf8' }, (err, blob) => {
if (err) return reject(err)
resolve(blob)
})
})
}
async function writeFile(path: string, data: string) {
return new Promise<void>((resolve, reject) => {
fs.writeFile(path, data, undefined, err => {
if (err) return reject(err)
resolve()
})
})
}
const commonOptions = {
dir,
fs,
}
const httpOptions = {
http,
url: `http://sharelatex/git/${projectId}`,
headers: {
Authorization: `Basic ${Buffer.from(`git:${token}`).toString('base64')}`,
},
}
const authorOptions = {
author: { name: 'user', email: 'user@example.com' },
committer: { name: 'user', email: 'user@example.com' },
}
// Clone
cy.then({ timeout: 10_000 }, async () => {
await git.clone({
...commonOptions,
...httpOptions,
})
})
const mainTex = `${dir}/main.tex`
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',
})
await git.push({
...commonOptions,
...httpOptions,
})
})
// 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,
})
expect(await readFile(mainTex)).to.equal(text + '% via editor\n')
})
})
})
})
})
function checkDisabled() {
ensureUserExists({ email: 'user@example.com' })
it('should not render the git-bridge UI in the settings', () => {
login('user@example.com')
cy.visit('/user/settings')
cy.findByText('Git Integration').should('not.exist')
})
it('should not render the git-bridge UI in the editor', function () {
login('user@example.com')
cy.visit('/project')
createProject('maybe git')
cy.get('header').findByText('Menu').click()
cy.findByText('Sync').should('not.exist')
cy.findByText('Git').should('not.exist')
})
}
describe('disabled in Server Pro', () => {
startWith({
pro: true,
})
checkDisabled()
})
describe('unavailable in CE', () => {
startWith({
pro: false,
vars: ENABLED_VARS,
})
checkDisabled()
})
})

View file

@ -0,0 +1,22 @@
/**
* Helper function for throttling clicks on the recompile button to avoid hitting server side rate limits.
* 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.
*/
export function throttledRecompile() {
let lastCompile = 0
function queueReset() {
cy.then(() => {
lastCompile = Date.now()
})
}
queueReset()
return () =>
cy.then(() => {
const msSinceLastCompile = Date.now() - lastCompile
cy.wait(Math.max(0, 3_000 - msSinceLastCompile))
cy.findByText('Recompile').click()
queueReset()
})
}

View file

@ -57,6 +57,8 @@ async function fetchJSON(
const { error, stdout, stderr } = await res.json() const { error, stdout, stderr } = await res.json()
if (error) { if (error) {
console.error(input, init, 'failed:', error) console.error(input, init, 'failed:', error)
if (stdout) console.log(stdout)
if (stderr) console.warn(stderr)
const err = new Error(error.message) const err = new Error(error.message)
Object.assign(err, error) Object.assign(err, error)
throw err throw err

View file

@ -1,4 +1,5 @@
const fs = require('fs') const fs = require('fs')
const Path = require('path')
const { execFile } = require('child_process') const { execFile } = require('child_process')
const express = require('express') const express = require('express')
const bodyParser = require('body-parser') const bodyParser = require('body-parser')
@ -9,8 +10,9 @@ const {
} = require('celebrate') } = require('celebrate')
const YAML = require('js-yaml') const YAML = require('js-yaml')
const FILES = { const PATHS = {
DOCKER_COMPOSE: 'docker-compose.override.yml', DOCKER_COMPOSE_OVERRIDE: 'docker-compose.override.yml',
SANDBOXED_COMPILES_HOST_DIR: Path.join(__dirname, 'cypress/compiles'),
} }
const IMAGES = { const IMAGES = {
CE: process.env.IMAGE_TAG_CE.replace(/:.+/, ''), CE: process.env.IMAGE_TAG_CE.replace(/:.+/, ''),
@ -21,7 +23,7 @@ let mongoIsInitialized = false
function readDockerComposeOverride() { function readDockerComposeOverride() {
try { try {
return YAML.load(fs.readFileSync(FILES.DOCKER_COMPOSE, 'utf-8')) return YAML.load(fs.readFileSync(PATHS.DOCKER_COMPOSE_OVERRIDE, 'utf-8'))
} catch (error) { } catch (error) {
if (error.code !== 'ENOENT') { if (error.code !== 'ENOENT') {
throw error throw error
@ -31,13 +33,14 @@ function readDockerComposeOverride() {
sharelatex: { sharelatex: {
environment: {}, environment: {},
}, },
'git-bridge': {},
}, },
} }
} }
} }
function writeDockerComposeOverride(cfg) { function writeDockerComposeOverride(cfg) {
fs.writeFileSync(FILES.DOCKER_COMPOSE, YAML.dump(cfg)) fs.writeFileSync(PATHS.DOCKER_COMPOSE_OVERRIDE, YAML.dump(cfg))
} }
const app = express() const app = express()
@ -95,6 +98,7 @@ function setVersionDockerCompose({ pro, version }) {
const cfg = readDockerComposeOverride() const cfg = readDockerComposeOverride()
cfg.services.sharelatex.image = `${pro ? IMAGES.PRO : IMAGES.CE}:${version}` cfg.services.sharelatex.image = `${pro ? IMAGES.PRO : IMAGES.CE}:${version}`
cfg.services['git-bridge'].image = `quay.io/sharelatex/git-bridge:${version}`
writeDockerComposeOverride(cfg) writeDockerComposeOverride(cfg)
} }
@ -128,17 +132,52 @@ app.post(
} }
) )
const allowedVars = Joi.object().keys({ const allowedVars = Joi.object(
OVERLEAF_APP_NAME: Joi.string(), Object.fromEntries(
OVERLEAF_LEFT_FOOTER: Joi.string(), [
OVERLEAF_RIGHT_FOOTER: Joi.string(), 'OVERLEAF_APP_NAME',
}) 'OVERLEAF_LEFT_FOOTER',
'OVERLEAF_RIGHT_FOOTER',
'OVERLEAF_PROXY_LEARN',
'GIT_BRIDGE_ENABLED',
'GIT_BRIDGE_HOST',
'GIT_BRIDGE_PORT',
'V1_HISTORY_URL',
'DOCKER_RUNNER',
'SANDBOXED_COMPILES',
'SANDBOXED_COMPILES_SIBLING_CONTAINERS',
'ALL_TEX_LIVE_DOCKER_IMAGE_NAMES',
].map(name => [name, Joi.string()])
)
)
function setVarsDockerCompose({ vars }) { function setVarsDockerCompose({ vars }) {
const cfg = readDockerComposeOverride() const cfg = readDockerComposeOverride()
cfg.services.sharelatex.environment = vars cfg.services.sharelatex.environment = vars
if (cfg.services.sharelatex.environment.GIT_BRIDGE_ENABLED === 'true') {
cfg.services.sharelatex.depends_on = ['git-bridge']
}
if (
cfg.services.sharelatex.environment
.SANDBOXED_COMPILES_SIBLING_CONTAINERS === 'true'
) {
cfg.services.sharelatex.environment.SANDBOXED_COMPILES_HOST_DIR =
PATHS.SANDBOXED_COMPILES_HOST_DIR
cfg.services.sharelatex.environment.TEX_LIVE_DOCKER_IMAGE =
process.env.TEX_LIVE_DOCKER_IMAGE
cfg.services.sharelatex.environment.ALL_TEX_LIVE_DOCKER_IMAGES =
process.env.ALL_TEX_LIVE_DOCKER_IMAGES
cfg.services.sharelatex.volumes = [
'/var/run/docker.sock:/var/run/docker.sock',
`${PATHS.SANDBOXED_COMPILES_HOST_DIR}:/var/lib/overleaf/data/compiles`,
]
} else {
cfg.services.sharelatex.volumes = []
}
writeDockerComposeOverride(cfg) writeDockerComposeOverride(cfg)
} }
@ -182,6 +221,7 @@ app.post(
'--timeout', '--timeout',
'0', '0',
'sharelatex', 'sharelatex',
'git-bridge',
'mongo', 'mongo',
'redis' 'redis'
), ),

View file

@ -0,0 +1,83 @@
import { startWith } from './helpers/config'
import { ensureUserExists, login } from './helpers/login'
describe('LearnWiki', function () {
const COPYING_A_PROJECT_URL = '/learn/how-to/Copying_a_project'
const UPLOADING_A_PROJECT_URL = '/learn/how-to/Uploading_a_project'
describe('enabled in Pro', () => {
startWith({
pro: true,
vars: {
OVERLEAF_PROXY_LEARN: 'true',
},
})
ensureUserExists({ email: 'user@example.com' })
it('should add a documentation entry to the nav bar', () => {
login('user@example.com')
cy.visit('/project')
cy.get('nav').findByText('Documentation')
})
it('should render wiki page', () => {
login('user@example.com')
cy.visit(UPLOADING_A_PROJECT_URL)
// Wiki content
cy.get('.page').findByText('Uploading a project')
cy.get('.page').contains(/how to create an Overleaf project/)
cy.get('img[alt="Creating a new project on Overleaf"]')
.should('be.visible')
.and((el: any) => {
expect(el[0].naturalWidth, 'renders image').to.be.greaterThan(0)
})
// Wiki navigation
cy.get('.contents').findByText('Copying a project')
})
it('should navigate back and forth', function () {
login('user@example.com')
cy.visit(COPYING_A_PROJECT_URL)
cy.get('.page').findByText('Copying a project')
cy.get('.contents').findByText('Uploading a project').click()
cy.url().should('contain', UPLOADING_A_PROJECT_URL)
cy.get('.page').findByText('Uploading a project')
cy.get('.contents').findByText('Copying a project').click()
cy.url().should('contain', COPYING_A_PROJECT_URL)
cy.get('.page').findByText('Copying a project')
})
})
describe('disabled in Pro', () => {
startWith({ pro: true })
checkDisabled()
})
describe('unavailable in CE', () => {
startWith({
pro: false,
vars: {
OVERLEAF_PROXY_LEARN: 'true',
},
})
checkDisabled()
})
function checkDisabled() {
ensureUserExists({ email: 'user@example.com' })
it('should not add a documentation entry to the nav bar', () => {
login('user@example.com')
cy.visit('/project')
cy.findByText('Documentation').should('not.exist')
})
it('should not render wiki page', () => {
login('user@example.com')
cy.visit(COPYING_A_PROJECT_URL, {
failOnStatusCode: false,
})
cy.findByText('Not found')
})
}
})

View file

@ -6,11 +6,13 @@
"": { "": {
"name": "@overleaf/server-ce/test", "name": "@overleaf/server-ce/test",
"dependencies": { "dependencies": {
"@isomorphic-git/lightning-fs": "^4.6.0",
"@testing-library/cypress": "^10.0.1", "@testing-library/cypress": "^10.0.1",
"body-parser": "^1.20.2", "body-parser": "^1.20.2",
"celebrate": "^15.0.3", "celebrate": "^15.0.3",
"cypress": "13.6.6", "cypress": "13.6.6",
"express": "^4.19.2", "express": "^4.19.2",
"isomorphic-git": "^1.25.10",
"js-yaml": "^4.1.0", "js-yaml": "^4.1.0",
"typescript": "^5.0.4" "typescript": "^5.0.4"
} }
@ -238,6 +240,25 @@
"@hapi/hoek": "^9.0.0" "@hapi/hoek": "^9.0.0"
} }
}, },
"node_modules/@isomorphic-git/idb-keyval": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/@isomorphic-git/idb-keyval/-/idb-keyval-3.3.2.tgz",
"integrity": "sha512-r8/AdpiS0/WJCNR/t/gsgL+M8NMVj/ek7s60uz3LmpCaTF2mEVlZJlB01ZzalgYzRLXwSPC92o+pdzjM7PN/pA=="
},
"node_modules/@isomorphic-git/lightning-fs": {
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/@isomorphic-git/lightning-fs/-/lightning-fs-4.6.0.tgz",
"integrity": "sha512-tfon8f1h6LawjFI/d8lZPWRPTxmdvyTMbkT/j5yo6dB0hALhKw5D9JsdCcUu/D1pAcMMiU7GZFDsDGqylerr7g==",
"dependencies": {
"@isomorphic-git/idb-keyval": "3.3.2",
"isomorphic-textencoder": "1.0.1",
"just-debounce-it": "1.1.0",
"just-once": "1.1.0"
},
"bin": {
"superblocktxt": "src/superblocktxt.js"
}
},
"node_modules/@sideway/address": { "node_modules/@sideway/address": {
"version": "4.1.5", "version": "4.1.5",
"resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz", "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz",
@ -469,6 +490,11 @@
"resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz", "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz",
"integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==" "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ=="
}, },
"node_modules/async-lock": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/async-lock/-/async-lock-1.4.1.tgz",
"integrity": "sha512-Az2ZTpuytrtqENulXwO3GGv1Bztugx6TT37NIo7imr/Qo0gsYiGtSdBa2B6fsXhTpVZDNfu1Qn3pk531e3q+nQ=="
},
"node_modules/asynckit": { "node_modules/asynckit": {
"version": "0.4.0", "version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
@ -719,6 +745,11 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/clean-git-ref": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/clean-git-ref/-/clean-git-ref-2.0.1.tgz",
"integrity": "sha512-bLSptAy2P0s6hU4PzuIMKmMJJSE6gLXGH1cntDu7bWJUksvuM+7ReOK61mozULErYvP6a15rnYl0zFDef+pyPw=="
},
"node_modules/clean-stack": { "node_modules/clean-stack": {
"version": "2.2.0", "version": "2.2.0",
"resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz",
@ -857,6 +888,17 @@
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
"integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==" "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ=="
}, },
"node_modules/crc-32": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz",
"integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==",
"bin": {
"crc32": "bin/crc32.njs"
},
"engines": {
"node": ">=0.8"
}
},
"node_modules/cross-spawn": { "node_modules/cross-spawn": {
"version": "7.0.3", "version": "7.0.3",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
@ -972,6 +1014,20 @@
} }
} }
}, },
"node_modules/decompress-response": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz",
"integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==",
"dependencies": {
"mimic-response": "^3.1.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/deep-equal": { "node_modules/deep-equal": {
"version": "2.2.2", "version": "2.2.2",
"resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.2.tgz", "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.2.tgz",
@ -1054,6 +1110,11 @@
"npm": "1.2.8000 || >= 1.4.16" "npm": "1.2.8000 || >= 1.4.16"
} }
}, },
"node_modules/diff3": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/diff3/-/diff3-0.0.3.tgz",
"integrity": "sha512-iSq8ngPOt0K53A6eVr4d5Kn6GNrM2nQZtC740pzIriHtn4pOQ2lyzEXQMBeVcWERN0ye7fhBsk9PbLLQOnUx/g=="
},
"node_modules/dom-accessibility-api": { "node_modules/dom-accessibility-api": {
"version": "0.5.16", "version": "0.5.16",
"resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz",
@ -1284,6 +1345,11 @@
"node >=0.6.0" "node >=0.6.0"
] ]
}, },
"node_modules/fast-text-encoding": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/fast-text-encoding/-/fast-text-encoding-1.0.6.tgz",
"integrity": "sha512-VhXlQgj9ioXCqGstD37E/HBeqEGV/qOD/kmbVG8h5xKBYvM1L3lR1Zn4555cQ8GkYbJa8aJSipLPndE1k6zK2w=="
},
"node_modules/fd-slicer": { "node_modules/fd-slicer": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz",
@ -1649,6 +1715,14 @@
} }
] ]
}, },
"node_modules/ignore": {
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz",
"integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==",
"engines": {
"node": ">= 4"
}
},
"node_modules/indent-string": { "node_modules/indent-string": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz",
@ -1976,6 +2050,46 @@
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="
}, },
"node_modules/isomorphic-git": {
"version": "1.25.10",
"resolved": "https://registry.npmjs.org/isomorphic-git/-/isomorphic-git-1.25.10.tgz",
"integrity": "sha512-IxGiaKBwAdcgBXwIcxJU6rHLk+NrzYaaPKXXQffcA0GW3IUrQXdUPDXDo+hkGVcYruuz/7JlGBiuaeTCgIgivQ==",
"dependencies": {
"async-lock": "^1.4.1",
"clean-git-ref": "^2.0.1",
"crc-32": "^1.2.0",
"diff3": "0.0.3",
"ignore": "^5.1.4",
"minimisted": "^2.0.0",
"pako": "^1.0.10",
"pify": "^4.0.1",
"readable-stream": "^3.4.0",
"sha.js": "^2.4.9",
"simple-get": "^4.0.1"
},
"bin": {
"isogit": "cli.cjs"
},
"engines": {
"node": ">=12"
}
},
"node_modules/isomorphic-git/node_modules/pify": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz",
"integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==",
"engines": {
"node": ">=6"
}
},
"node_modules/isomorphic-textencoder": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/isomorphic-textencoder/-/isomorphic-textencoder-1.0.1.tgz",
"integrity": "sha512-676hESgHullDdHDsj469hr+7t3i/neBKU9J7q1T4RHaWwLAsaQnywC0D1dIUId0YZ+JtVrShzuBk1soo0+GVcQ==",
"dependencies": {
"fast-text-encoding": "^1.0.0"
}
},
"node_modules/isstream": { "node_modules/isstream": {
"version": "0.1.2", "version": "0.1.2",
"resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
@ -2049,6 +2163,16 @@
"verror": "1.10.0" "verror": "1.10.0"
} }
}, },
"node_modules/just-debounce-it": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/just-debounce-it/-/just-debounce-it-1.1.0.tgz",
"integrity": "sha512-87Nnc0qZKgBZuhFZjYVjSraic0x7zwjhaTMrCKlj0QYKH6lh0KbFzVnfu6LHan03NO7J8ygjeBeD0epejn5Zcg=="
},
"node_modules/just-once": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/just-once/-/just-once-1.1.0.tgz",
"integrity": "sha512-+rZVpl+6VyTilK7vB/svlMPil4pxqIJZkbnN7DKZTOzyXfun6ZiFeq2Pk4EtCEHZ0VU4EkdFzG8ZK5F3PErcDw=="
},
"node_modules/lazy-ass": { "node_modules/lazy-ass": {
"version": "1.6.0", "version": "1.6.0",
"resolved": "https://registry.npmjs.org/lazy-ass/-/lazy-ass-1.6.0.tgz", "resolved": "https://registry.npmjs.org/lazy-ass/-/lazy-ass-1.6.0.tgz",
@ -2237,6 +2361,17 @@
"node": ">=6" "node": ">=6"
} }
}, },
"node_modules/mimic-response": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz",
"integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==",
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/minimatch": { "node_modules/minimatch": {
"version": "3.1.2", "version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
@ -2256,6 +2391,14 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/minimisted": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/minimisted/-/minimisted-2.0.1.tgz",
"integrity": "sha512-1oPjfuLQa2caorJUM8HV8lGgWCc0qqAO1MNv/k05G4qslmsndV/5WdNZrqCiyqiz3wohia2Ij2B7w2Dr7/IyrA==",
"dependencies": {
"minimist": "^1.2.5"
}
},
"node_modules/ms": { "node_modules/ms": {
"version": "2.1.2", "version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
@ -2380,6 +2523,11 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/pako": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz",
"integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="
},
"node_modules/parseurl": { "node_modules/parseurl": {
"version": "1.3.3", "version": "1.3.3",
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
@ -2555,6 +2703,19 @@
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
"integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="
}, },
"node_modules/readable-stream": {
"version": "3.6.2",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
"integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
"dependencies": {
"inherits": "^2.0.3",
"string_decoder": "^1.1.1",
"util-deprecate": "^1.0.1"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/regenerator-runtime": { "node_modules/regenerator-runtime": {
"version": "0.14.0", "version": "0.14.0",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz",
@ -2753,6 +2914,18 @@
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
"integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="
}, },
"node_modules/sha.js": {
"version": "2.4.11",
"resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz",
"integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==",
"dependencies": {
"inherits": "^2.0.1",
"safe-buffer": "^5.0.1"
},
"bin": {
"sha.js": "bin.js"
}
},
"node_modules/shebang-command": { "node_modules/shebang-command": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
@ -2790,6 +2963,49 @@
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
"integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="
}, },
"node_modules/simple-concat": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz",
"integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
]
},
"node_modules/simple-get": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz",
"integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"dependencies": {
"decompress-response": "^6.0.0",
"once": "^1.3.1",
"simple-concat": "^1.0.0"
}
},
"node_modules/slice-ansi": { "node_modules/slice-ansi": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz", "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz",
@ -2846,6 +3062,14 @@
"node": ">= 0.4" "node": ">= 0.4"
} }
}, },
"node_modules/string_decoder": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
"integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
"dependencies": {
"safe-buffer": "~5.2.0"
}
},
"node_modules/string-width": { "node_modules/string-width": {
"version": "4.2.3", "version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
@ -3035,6 +3259,11 @@
"requires-port": "^1.0.0" "requires-port": "^1.0.0"
} }
}, },
"node_modules/util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
},
"node_modules/utils-merge": { "node_modules/utils-merge": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",

View file

@ -9,11 +9,13 @@
"format:fix": "prettier --write $PWD/'**/*.{js,mjs,ts,tsx,json}'" "format:fix": "prettier --write $PWD/'**/*.{js,mjs,ts,tsx,json}'"
}, },
"dependencies": { "dependencies": {
"@isomorphic-git/lightning-fs": "^4.6.0",
"@testing-library/cypress": "^10.0.1", "@testing-library/cypress": "^10.0.1",
"body-parser": "^1.20.2", "body-parser": "^1.20.2",
"celebrate": "^15.0.3", "celebrate": "^15.0.3",
"cypress": "13.6.6", "cypress": "13.6.6",
"express": "^4.19.2", "express": "^4.19.2",
"isomorphic-git": "^1.25.10",
"js-yaml": "^4.1.0", "js-yaml": "^4.1.0",
"typescript": "^5.0.4" "typescript": "^5.0.4"
} }

View file

@ -0,0 +1,79 @@
import { ensureUserExists, login } from './helpers/login'
import { createProject } from './helpers/project'
import { startWith } from './helpers/config'
import { throttledRecompile } from './helpers/compile'
describe('SandboxedCompiles', function () {
ensureUserExists({ email: 'user@example.com' })
const enabledVars = {
DOCKER_RUNNER: 'true',
SANDBOXED_COMPILES: 'true',
SANDBOXED_COMPILES_SIBLING_CONTAINERS: 'true',
ALL_TEX_LIVE_DOCKER_IMAGE_NAMES: '2023,2022',
}
describe('enabled in Server Pro', () => {
startWith({
pro: true,
vars: enabledVars,
})
beforeEach(function () {
login('user@example.com')
})
it('should offer TexLive images and switch the compiler', () => {
cy.visit('/project')
createProject('sandboxed')
const recompile = throttledRecompile()
// check produced PDF
cy.get('.pdf-viewer').should('contain.text', 'sandboxed')
cy.get('[aria-label="View logs"]').click()
cy.findByText(/This is pdfTeX, Version .+ \(TeX Live 2023\) /)
cy.get('header').findByText('Menu').click()
cy.findByText('TeX Live version')
.parent()
.findByText('2023')
.parent()
.select('2022')
// close editor menu
cy.get('#left-menu-modal').click()
// Trigger compile with other TexLive version
recompile()
cy.get('[aria-label="View logs"]').click()
cy.findByText(/This is pdfTeX, Version .+ \(TeX Live 2022\) /)
})
})
function checkUsesDefaultCompiler() {
beforeEach(function () {
login('user@example.com')
})
it('should not offer TexLive images and use default compiler', () => {
cy.visit('/project')
createProject('sandboxed')
// check produced PDF
cy.get('.pdf-viewer').should('contain.text', 'sandboxed')
cy.get('[aria-label="View logs"]').click()
cy.findByText(/This is pdfTeX, Version .+ \(TeX Live 2024\) /)
cy.get('header').findByText('Menu').click()
cy.findByText('TeX Live version').should('not.exist')
})
}
describe('disabled in Server Pro', () => {
startWith({ pro: true })
checkUsesDefaultCompiler()
})
describe.skip('unavailable in CE', () => {
startWith({ pro: false, vars: enabledVars })
checkUsesDefaultCompiler()
})
})