From 41d120d8f18bcd633238c1f708dc54e353cb5fde Mon Sep 17 00:00:00 2001 From: Tim Down <158919+timdown@users.noreply.github.com> Date: Tue, 18 Nov 2025 10:57:17 +0000 Subject: [PATCH] Merge pull request #29038 from overleaf/td-eslint-e2e-tests Enable ESLint for all end-to-end tests GitOrigin-RevId: 5d085f52fabcc794b0457edbbb551500477d4110 --- package-lock.json | 145 ++++++++---------- server-ce/.eslintrc | 4 + server-ce/config/settings.js | 6 +- server-ce/test/Jenkinsfile | 20 ++- server-ce/test/accounts.spec.ts | 2 +- server-ce/test/admin.spec.ts | 66 ++++---- .../test/create-and-compile-project.spec.ts | 3 +- server-ce/test/customization.spec.ts | 14 +- server-ce/test/cypress.config.ts | 2 +- .../test/cypress/support/{e2e.js => e2e.ts} | 0 server-ce/test/editor.spec.ts | 38 ++--- server-ce/test/external-auth.spec.ts | 8 +- server-ce/test/filestore-migration.spec.ts | 4 +- server-ce/test/git-bridge.spec.ts | 29 ++-- server-ce/test/graceful-shutdown.spec.ts | 2 +- .../helpers/beforeWithReRunOnTestRetry.ts | 2 +- server-ce/test/helpers/email.ts | 7 +- server-ce/test/helpers/hostAdminClient.ts | 2 +- server-ce/test/helpers/project.ts | 13 +- server-ce/test/helpers/read-file.ts | 22 ++- server-ce/test/history.spec.ts | 2 +- server-ce/test/learn-wiki.spec.ts | 19 ++- server-ce/test/package.json | 7 +- server-ce/test/project-list.spec.ts | 18 +-- server-ce/test/project-sharing.spec.ts | 68 ++++---- server-ce/test/sandboxed-compiles.spec.ts | 2 + server-ce/test/templates.spec.ts | 23 ++- server-ce/test/tsconfig.json | 5 +- server-ce/test/upgrading.spec.ts | 20 +-- 29 files changed, 288 insertions(+), 265 deletions(-) rename server-ce/test/cypress/support/{e2e.js => e2e.ts} (100%) diff --git a/package-lock.json b/package-lock.json index 820bc8cace..215239f2c0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13174,32 +13174,35 @@ ] }, "node_modules/@napi-rs/canvas": { - "version": "0.1.68", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas/-/canvas-0.1.68.tgz", - "integrity": "sha512-LQESrePLEBLvhuFkXx9jjBXRC2ClYsO5mqQ1m/puth5z9SOuM3N/B3vDuqnC3RJFktDktyK9khGvo7dTkqO9uQ==", + "version": "0.1.80", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas/-/canvas-0.1.80.tgz", + "integrity": "sha512-DxuT1ClnIPts1kQx8FBmkk4BQDTfI5kIzywAaMjQSXfNnra5UFU9PwurXrl+Je3bJ6BGsp/zmshVVFbCmyI+ww==", "dev": true, "license": "MIT", "optional": true, + "workspaces": [ + "e2e/*" + ], "engines": { "node": ">= 10" }, "optionalDependencies": { - "@napi-rs/canvas-android-arm64": "0.1.68", - "@napi-rs/canvas-darwin-arm64": "0.1.68", - "@napi-rs/canvas-darwin-x64": "0.1.68", - "@napi-rs/canvas-linux-arm-gnueabihf": "0.1.68", - "@napi-rs/canvas-linux-arm64-gnu": "0.1.68", - "@napi-rs/canvas-linux-arm64-musl": "0.1.68", - "@napi-rs/canvas-linux-riscv64-gnu": "0.1.68", - "@napi-rs/canvas-linux-x64-gnu": "0.1.68", - "@napi-rs/canvas-linux-x64-musl": "0.1.68", - "@napi-rs/canvas-win32-x64-msvc": "0.1.68" + "@napi-rs/canvas-android-arm64": "0.1.80", + "@napi-rs/canvas-darwin-arm64": "0.1.80", + "@napi-rs/canvas-darwin-x64": "0.1.80", + "@napi-rs/canvas-linux-arm-gnueabihf": "0.1.80", + "@napi-rs/canvas-linux-arm64-gnu": "0.1.80", + "@napi-rs/canvas-linux-arm64-musl": "0.1.80", + "@napi-rs/canvas-linux-riscv64-gnu": "0.1.80", + "@napi-rs/canvas-linux-x64-gnu": "0.1.80", + "@napi-rs/canvas-linux-x64-musl": "0.1.80", + "@napi-rs/canvas-win32-x64-msvc": "0.1.80" } }, "node_modules/@napi-rs/canvas-android-arm64": { - "version": "0.1.68", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas-android-arm64/-/canvas-android-arm64-0.1.68.tgz", - "integrity": "sha512-h1KcSR4LKLfRfzeBH65xMxbWOGa1OtMFQbCMVlxPCkN1Zr+2gK+70pXO5ktojIYcUrP6KDcOwoc8clho5ccM/w==", + "version": "0.1.80", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-android-arm64/-/canvas-android-arm64-0.1.80.tgz", + "integrity": "sha512-sk7xhN/MoXeuExlggf91pNziBxLPVUqF2CAVnB57KLG/pz7+U5TKG8eXdc3pm0d7Od0WreB6ZKLj37sX9muGOQ==", "cpu": [ "arm64" ], @@ -13214,9 +13217,9 @@ } }, "node_modules/@napi-rs/canvas-darwin-arm64": { - "version": "0.1.68", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas-darwin-arm64/-/canvas-darwin-arm64-0.1.68.tgz", - "integrity": "sha512-/VURlrAD4gDoxW1GT/b0nP3fRz/fhxmHI/xznTq2FTwkQLPOlLkDLCvTmQ7v6LtGKdc2Ed6rvYpRan+JXThInQ==", + "version": "0.1.80", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-darwin-arm64/-/canvas-darwin-arm64-0.1.80.tgz", + "integrity": "sha512-O64APRTXRUiAz0P8gErkfEr3lipLJgM6pjATwavZ22ebhjYl/SUbpgM0xcWPQBNMP1n29afAC/Us5PX1vg+JNQ==", "cpu": [ "arm64" ], @@ -13231,9 +13234,9 @@ } }, "node_modules/@napi-rs/canvas-darwin-x64": { - "version": "0.1.68", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas-darwin-x64/-/canvas-darwin-x64-0.1.68.tgz", - "integrity": "sha512-tEpvGR6vCLTo1Tx9wmDnoOKROpw57wiCWwCpDOuVlj/7rqEJOUYr9ixW4aRJgmeGBrZHgevI0EURys2ER6whmg==", + "version": "0.1.80", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-darwin-x64/-/canvas-darwin-x64-0.1.80.tgz", + "integrity": "sha512-FqqSU7qFce0Cp3pwnTjVkKjjOtxMqRe6lmINxpIZYaZNnVI0H5FtsaraZJ36SiTHNjZlUB69/HhxNDT1Aaa9vA==", "cpu": [ "x64" ], @@ -13248,9 +13251,9 @@ } }, "node_modules/@napi-rs/canvas-linux-arm-gnueabihf": { - "version": "0.1.68", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm-gnueabihf/-/canvas-linux-arm-gnueabihf-0.1.68.tgz", - "integrity": "sha512-U9xbJsumPOiAYeAFZMlHf62b9dGs2HJ6Q5xt7xTB0uEyPeurwhgYBWGgabdsEidyj38YuzI/c3LGBbSQB3vagw==", + "version": "0.1.80", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm-gnueabihf/-/canvas-linux-arm-gnueabihf-0.1.80.tgz", + "integrity": "sha512-eyWz0ddBDQc7/JbAtY4OtZ5SpK8tR4JsCYEZjCE3dI8pqoWUC8oMwYSBGCYfsx2w47cQgQCgMVRVTFiiO38hHQ==", "cpu": [ "arm" ], @@ -13265,9 +13268,9 @@ } }, "node_modules/@napi-rs/canvas-linux-arm64-gnu": { - "version": "0.1.68", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm64-gnu/-/canvas-linux-arm64-gnu-0.1.68.tgz", - "integrity": "sha512-KFkn8wEm3mPnWD4l8+OUUkxylSJuN5q9PnJRZJgv15RtCA1bgxIwTkBhI/+xuyVMcHqON9sXq7cDkEJtHm35dg==", + "version": "0.1.80", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm64-gnu/-/canvas-linux-arm64-gnu-0.1.80.tgz", + "integrity": "sha512-qwA63t8A86bnxhuA/GwOkK3jvb+XTQaTiVML0vAWoHyoZYTjNs7BzoOONDgTnNtr8/yHrq64XXzUoLqDzU+Uuw==", "cpu": [ "arm64" ], @@ -13282,9 +13285,9 @@ } }, "node_modules/@napi-rs/canvas-linux-arm64-musl": { - "version": "0.1.68", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm64-musl/-/canvas-linux-arm64-musl-0.1.68.tgz", - "integrity": "sha512-IQzts91rCdOALXBWQxLZRCEDrfFTGDtNRJMNu+2SKZ1uT8cmPQkPwVk5rycvFpvgAcmiFiOSCp1aRrlfU8KPpQ==", + "version": "0.1.80", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-arm64-musl/-/canvas-linux-arm64-musl-0.1.80.tgz", + "integrity": "sha512-1XbCOz/ymhj24lFaIXtWnwv/6eFHXDrjP0jYkc6iHQ9q8oXKzUX1Lc6bu+wuGiLhGh2GS/2JlfORC5ZcXimRcg==", "cpu": [ "arm64" ], @@ -13299,9 +13302,9 @@ } }, "node_modules/@napi-rs/canvas-linux-riscv64-gnu": { - "version": "0.1.68", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-riscv64-gnu/-/canvas-linux-riscv64-gnu-0.1.68.tgz", - "integrity": "sha512-e9AS5UttoIKqXSmBzKZdd3NErSVyOEYzJfNOCGtafGk1//gibTwQXGlSXmAKuErqMp09pyk9aqQRSYzm1AQfBw==", + "version": "0.1.80", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-riscv64-gnu/-/canvas-linux-riscv64-gnu-0.1.80.tgz", + "integrity": "sha512-XTzR125w5ZMs0lJcxRlS1K3P5RaZ9RmUsPtd1uGt+EfDyYMu4c6SEROYsxyatbbu/2+lPe7MPHOO/0a0x7L/gw==", "cpu": [ "riscv64" ], @@ -13316,9 +13319,9 @@ } }, "node_modules/@napi-rs/canvas-linux-x64-gnu": { - "version": "0.1.68", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-x64-gnu/-/canvas-linux-x64-gnu-0.1.68.tgz", - "integrity": "sha512-Pa/I36VE3j57I3Obhrr+J48KGFfkZk2cJN/2NmW/vCgmoF7kCP6aTVq5n+cGdGWLd/cN9CJ9JvNwEoMRDghu0g==", + "version": "0.1.80", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-x64-gnu/-/canvas-linux-x64-gnu-0.1.80.tgz", + "integrity": "sha512-BeXAmhKg1kX3UCrJsYbdQd3hIMDH/K6HnP/pG2LuITaXhXBiNdh//TVVVVCBbJzVQaV5gK/4ZOCMrQW9mvuTqA==", "cpu": [ "x64" ], @@ -13333,9 +13336,9 @@ } }, "node_modules/@napi-rs/canvas-linux-x64-musl": { - "version": "0.1.68", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-x64-musl/-/canvas-linux-x64-musl-0.1.68.tgz", - "integrity": "sha512-9c6rkc5195wNxuUHJdf4/mmnq433OQey9TNvQ9LspJazvHbfSkTij8wtKjASVQsJyPDva4fkWOeV/OQ7cLw0GQ==", + "version": "0.1.80", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-linux-x64-musl/-/canvas-linux-x64-musl-0.1.80.tgz", + "integrity": "sha512-x0XvZWdHbkgdgucJsRxprX/4o4sEed7qo9rCQA9ugiS9qE2QvP0RIiEugtZhfLH3cyI+jIRFJHV4Fuz+1BHHMg==", "cpu": [ "x64" ], @@ -13350,9 +13353,9 @@ } }, "node_modules/@napi-rs/canvas-win32-x64-msvc": { - "version": "0.1.68", - "resolved": "https://registry.npmjs.org/@napi-rs/canvas-win32-x64-msvc/-/canvas-win32-x64-msvc-0.1.68.tgz", - "integrity": "sha512-Fc5Dez23u0FoSATurT6/w1oMytiRnKWEinHivdMvXpge6nG4YvhrASrtqMk8dGJMVQpHr8QJYF45rOrx2YU2Aw==", + "version": "0.1.80", + "resolved": "https://registry.npmjs.org/@napi-rs/canvas-win32-x64-msvc/-/canvas-win32-x64-msvc-0.1.80.tgz", + "integrity": "sha512-Z8jPsM6df5V8B1HrCHB05+bDiCxjE9QA//3YrkKIdVDEwn5RKaqOxCJDRJkl48cJbylcrJbW4HxZbTte8juuPg==", "cpu": [ "x64" ], @@ -20010,16 +20013,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/pdf-parse": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@types/pdf-parse/-/pdf-parse-1.1.5.tgz", - "integrity": "sha512-kBfrSXsloMnUJOKi25s3+hRmkycHfLK6A09eRGqF/N8BkQoPUmaCr+q8Cli5FnfohEz/rsv82zAiPz/LXtOGhA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@types/pg": { "version": "8.6.1", "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.6.1.tgz", @@ -39620,13 +39613,6 @@ "node": ">=10.5.0" } }, - "node_modules/node-ensure": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/node-ensure/-/node-ensure-0.0.0.tgz", - "integrity": "sha512-DRI60hzo2oKN1ma0ckc6nQWlHU69RH6xN0sjQTjMpChPfTYvKZdcQFfdYK2RWbJcKyUizSIy/l8OTGxMAM1QDw==", - "dev": true, - "license": "MIT" - }, "node_modules/node-fetch": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", @@ -41147,27 +41133,32 @@ "integrity": "sha1-HUCLP9t2kjuVQ9lvtMnf1TXZy10=" }, "node_modules/pdf-parse": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/pdf-parse/-/pdf-parse-1.1.1.tgz", - "integrity": "sha512-v6ZJ/efsBpGrGGknjtq9J/oC8tZWq0KWL5vQrk2GlzLEQPUDB1ex+13Rmidl1neNN358Jn9EHZw5y07FFtaC7A==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pdf-parse/-/pdf-parse-2.3.0.tgz", + "integrity": "sha512-VRKvhqdZ694CjdR1vusZ7VIA7ZuMN/GQ7eKz+e3z9ujCQdCQMOEG9x6cHfq8ddS7XspXVrruWuKmXm8g0hFlSQ==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "debug": "^3.1.0", - "node-ensure": "^0.0.0" + "pdfjs-dist": "^5.4.296" }, "engines": { - "node": ">=6.8.1" + "node": ">=20.16.0 <21 || >=22.3.0" + }, + "optionalDependencies": { + "@napi-rs/canvas": "^0.1.80" } }, - "node_modules/pdf-parse/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "node_modules/pdf-parse/node_modules/pdfjs-dist": { + "version": "5.4.296", + "resolved": "https://registry.npmjs.org/pdfjs-dist/-/pdfjs-dist-5.4.296.tgz", + "integrity": "sha512-DlOzet0HO7OEnmUmB6wWGJrrdvbyJKftI1bhMitK7O2N8W2gc757yyYBbINy9IDafXAV9wmKr9t7xsTaNKRG5Q==", "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.1" + "license": "Apache-2.0", + "engines": { + "node": ">=20.16.0 || >=22.3.0" + }, + "optionalDependencies": { + "@napi-rs/canvas": "^0.1.80" } }, "node_modules/pdfjs-dist": { @@ -53269,7 +53260,6 @@ "@overleaf/validation-tools": "*", "@testing-library/cypress": "^10.0.3", "@types/adm-zip": "^0.5.7", - "@types/pdf-parse": "^1.1.5", "@types/uuid": "^9.0.8", "adm-zip": "^0.5.12", "body-parser": "^1.20.3", @@ -53279,7 +53269,7 @@ "isomorphic-git": "^1.33.1", "js-yaml": "^4.1.1", "mocha-junit-reporter": "^2.2.1", - "pdf-parse": "^1.1.1", + "pdf-parse": "^2.3.0", "uuid": "^9.0.1", "zod-validation-error": "^4.0.1" } @@ -56646,7 +56636,6 @@ "@isomorphic-git/lightning-fs": "^4.6.0", "@testing-library/cypress": "^10.0.3", "@types/adm-zip": "^0.5.7", - "@types/pdf-parse": "^1.1.5", "@types/uuid": "^9.0.8", "adm-zip": "^0.5.12", "cypress": "13.13.2", @@ -56654,7 +56643,7 @@ "isomorphic-git": "^1.33.1", "mailtrap": "^4.3.0", "mocha-junit-reporter": "^2.2.1", - "pdf-parse": "^1.1.1", + "pdf-parse": "^2.3.0", "typescript": "^5.0.4", "uuid": "^9.0.1" } diff --git a/server-ce/.eslintrc b/server-ce/.eslintrc index fc58ef3e98..ccbc181329 100644 --- a/server-ce/.eslintrc +++ b/server-ce/.eslintrc @@ -21,5 +21,9 @@ "overrides": [ // Extra rules for Cypress tests { "files": ["**/*.spec.ts"], "extends": ["plugin:cypress/recommended"] } + ], + "ignorePatterns": [ + "hotfix/", + "develop/" ] } diff --git a/server-ce/config/settings.js b/server-ce/config/settings.js index f9813fa9c4..640f1da0ce 100644 --- a/server-ce/config/settings.js +++ b/server-ce/config/settings.js @@ -14,7 +14,7 @@ */ let redisConfig, siteUrl let e -const Path = require('path') +const Path = require('node:path') // These credentials are used for authenticating api requests // between services that may need to go over public channels @@ -491,9 +491,9 @@ if ( // With lots of incoming and outgoing HTTP connections to different services, // sometimes long running, it is a good idea to increase the default number // of sockets that Node will hold open. -const http = require('http') +const http = require('node:http') http.globalAgent.maxSockets = 300 -const https = require('https') +const https = require('node:https') https.globalAgent.maxSockets = 300 module.exports = settings diff --git a/server-ce/test/Jenkinsfile b/server-ce/test/Jenkinsfile index 2db81c315b..c4e9c4e1e7 100644 --- a/server-ce/test/Jenkinsfile +++ b/server-ce/test/Jenkinsfile @@ -75,6 +75,24 @@ pipeline { sh 'bin/run -w /overleaf/server-ce/test monorepo npm run format' } } + stage('Lint') { + steps { + script { + waitUntil { + return job_npm_install_done + } + } + sh 'bin/run -w /overleaf/server-ce/test monorepo npm run lint -- --format json --output-file reports/eslint.json' + } + post { + always { + sh """ + sed -i 's_"filePath":"/overleaf_"filePath":"/workspace_g' server-ce/test/reports/eslint.json + """ + recordIssues checksAnnotationScope: 'ALL', enabledForFailure: true, failOnError: true, id: 'server-pro-e2e-tests-eslint', name: 'Server-Pro-E2E-Tests eslint', qualityGates: [[integerThreshold: 1, threshold: 1.0, type: 'TOTAL']], sourceCodeRetention: 'LAST_BUILD', tools: [esLint(pattern: 'server-ce/test/reports/eslint.json')] + } + } + } stage('Copybara') { steps { sh 'copybara/bin/sync' @@ -337,7 +355,7 @@ pipeline { post { // Collect junit test results for both success and failure case. always { - junit checksName: 'Server Pro E2E test results', testResults: 'server-ce/test/reports/junit-*.xml' + junit checksName: 'Server-Pro-E2E-Tests results', testResults: 'server-ce/test/reports/junit-*.xml' } // Ensure tear down of test containers, remove CE docker images, then run general Jenkins VM cleanup. cleanup { diff --git a/server-ce/test/accounts.spec.ts b/server-ce/test/accounts.spec.ts index 92327a24d9..5ea8ec8569 100644 --- a/server-ce/test/accounts.spec.ts +++ b/server-ce/test/accounts.spec.ts @@ -16,7 +16,7 @@ describe('Accounts', function () { cy.url().should('include', '/login') }) - it('should render the email on the user activate screen', () => { + it('should render the email on the user activate screen', function () { const email = 'not-activated-user@example.com' cy.then(async () => { const { url } = await createMongoUser({ email }) diff --git a/server-ce/test/admin.spec.ts b/server-ce/test/admin.spec.ts index ef4c4a84ac..2f8ad57169 100644 --- a/server-ce/test/admin.spec.ts +++ b/server-ce/test/admin.spec.ts @@ -12,7 +12,7 @@ import { openEmail } from './helpers/email' describe('admin panel', function () { function registrationTests() { - it('via GUI and opening URL manually', () => { + it('via GUI and opening URL manually', function () { const user = `${uuid()}@example.com` cy.findByLabelText('Emails to register new users').type(user + '{enter}') @@ -24,7 +24,7 @@ describe('admin panel', function () { }) }) - it('via GUI and email', () => { + it('via GUI and email', function () { const user = `${uuid()}@example.com` cy.findByLabelText('Emails to register new users').type(user + '{enter}') @@ -49,7 +49,7 @@ describe('admin panel', function () { activateUser(url) }) }) - it('via script and opening URL manually', () => { + it('via script and opening URL manually', function () { const user = `${uuid()}@example.com` let url: string cy.then(async () => { @@ -59,7 +59,7 @@ describe('admin panel', function () { activateUser(url) }) }) - it('via script and email', () => { + it('via script and email', function () { const user = `${uuid()}@example.com` let url: string cy.then(async () => { @@ -81,7 +81,7 @@ describe('admin panel', function () { }) } - describe('in CE', () => { + describe('in CE', function () { if (isExcludedBySharding('CE_DEFAULT')) return startWith({ pro: false, version: 'latest' }) const admin = 'admin@example.com' @@ -89,8 +89,8 @@ describe('admin panel', function () { ensureUserExists({ email: admin, isAdmin: true }) ensureUserExists({ email: user }) - describe('create users', () => { - beforeEach(() => { + describe('create users', function () { + beforeEach(function () { login(admin) cy.visit('/project') cy.findByRole('menuitem', { name: 'Admin' }).click() @@ -100,7 +100,7 @@ describe('admin panel', function () { }) }) - describe('in server pro', () => { + describe('in server pro', function () { const admin = 'admin@example.com' const user1 = 'user@example.com' const user2 = 'user2@example.com' @@ -135,13 +135,13 @@ describe('admin panel', function () { ) }) - describe('admin menu items', () => { - beforeEach(() => { + describe('admin menu items', function () { + beforeEach(function () { login(admin) cy.visit('/project') }) - it('displays expected admin menu items', () => { + it('displays expected admin menu items', function () { const menuitems = ['Manage Site', 'Manage Users', 'Project URL Lookup'] menuitems.forEach(name => { cy.findByRole('menuitem', { name: 'Admin' }).click() @@ -153,15 +153,15 @@ describe('admin panel', function () { }) }) - describe('manage site', () => { - beforeEach(() => { + describe('manage site', function () { + beforeEach(function () { login(admin) cy.visit('/project') cy.findByRole('menuitem', { name: 'Admin' }).click() cy.findByRole('menuitem', { name: 'Manage Site' }).click() }) - it('publish and clear admin messages', () => { + it('publish and clear admin messages', function () { const message = 'Admin Message ' + uuid() cy.log('create system message') @@ -189,15 +189,15 @@ describe('admin panel', function () { }) }) - describe('manage users', () => { - beforeEach(() => { + describe('manage users', function () { + beforeEach(function () { login(admin) cy.visit('/project') cy.findByRole('menuitem', { name: 'Admin' }).click() cy.findByRole('menuitem', { name: 'Manage Users' }).click() }) - it('displays expected tabs', () => { + it('displays expected tabs', function () { const tabs = ['Users', 'License Usage'] cy.findAllByRole('tab').should('have.length', tabs.length) tabs.forEach(tabName => { @@ -205,21 +205,21 @@ describe('admin panel', function () { }) }) - it('license usage tab', () => { + it('license usage tab', function () { cy.get('a').contains('License Usage').click() cy.findByText( 'An active user is one who has opened a project in this Server Pro instance in the last 12 months.' ) }) - describe('create users', () => { - beforeEach(() => { + describe('create users', function () { + beforeEach(function () { cy.get('a').contains('New User').click() }) registrationTests() }) - it('user list RegExp search', () => { + it('user list RegExp search', function () { cy.findByLabelText('RegExp').click() cy.findByPlaceholderText('Search users by email or id…').type( 'user[0-9]{enter}' @@ -229,8 +229,8 @@ describe('admin panel', function () { }) }) - describe('user page', () => { - beforeEach(() => { + describe('user page', function () { + beforeEach(function () { login(admin) cy.visit('/project') cy.findByRole('menuitem', { name: 'Admin' }).click() @@ -242,7 +242,7 @@ describe('admin panel', function () { cy.url().should('match', /\/admin\/user\/[a-fA-F0-9]{24}/) }) - it('displays expected tabs', () => { + it('displays expected tabs', function () { const tabs = [ 'User Info', 'Projects', @@ -256,18 +256,18 @@ describe('admin panel', function () { }) }) - describe('user info tab', () => { - beforeEach(() => { + describe('user info tab', function () { + beforeEach(function () { cy.findByRole('tab', { name: 'User Info' }).click() }) - it('displays required sections', () => { + it('displays required sections', function () { // not exhaustive list, checks the tab content is rendered cy.findByText('Profile') cy.findByText('Editor Settings') }) - it('should not display SaaS-only sections', () => { + it('should not display SaaS-only sections', function () { cy.findByLabelText('Referred User Count').should('not.exist') cy.findByRole('heading', { name: /Split Test Assignments/ }).should( 'not.exist' @@ -285,7 +285,7 @@ describe('admin panel', function () { }) }) - it('transfer project ownership', () => { + it('transfer project ownership', function () { cy.log("access project admin through owners' project list") cy.findByRole('tablist').within(() => { cy.findByRole('tab', { name: 'Projects' }).click() @@ -326,13 +326,13 @@ describe('admin panel', function () { }) }) - describe('project page', () => { - beforeEach(() => { + describe('project page', function () { + beforeEach(function () { login(admin) cy.visit(`/admin/project/${testProjectId}`) }) - it('displays expected tabs', () => { + it('displays expected tabs', function () { const tabs = ['Project Info', 'Deleted Docs', 'Audit Log'] cy.findAllByRole('tab').should('have.length', tabs.length) tabs.forEach(tabName => { @@ -341,7 +341,7 @@ describe('admin panel', function () { }) }) - it('restore deleted projects', () => { + it('restore deleted projects', function () { login(user1) cy.visit('/project') diff --git a/server-ce/test/create-and-compile-project.spec.ts b/server-ce/test/create-and-compile-project.spec.ts index 62852d0611..43c853d5fd 100644 --- a/server-ce/test/create-and-compile-project.spec.ts +++ b/server-ce/test/create-and-compile-project.spec.ts @@ -42,7 +42,8 @@ describe('Project creation and compilation', function () { .findByRole('button', { name: 'New file' }) .click() cy.findByRole('dialog').within(() => { - cy.findByLabelText('File Name').clear().type(fileName) + cy.findByLabelText('File Name').as('filename').clear() + cy.get('@filename').type(fileName) cy.findByRole('button', { name: 'Create' }).click() }) cy.findByRole('button', { name: fileName }).click() diff --git a/server-ce/test/customization.spec.ts b/server-ce/test/customization.spec.ts index feabe0cc9e..d296c57c6b 100644 --- a/server-ce/test/customization.spec.ts +++ b/server-ce/test/customization.spec.ts @@ -1,18 +1,18 @@ import { isExcludedBySharding, startWith } from './helpers/config' -describe('Customization', () => { +describe('Customization', function () { if (isExcludedBySharding('CE_CUSTOM_1')) return - describe('default settings', () => { + describe('default settings', function () { startWith({}) - it('should display the default right footer', () => { + it('should display the default right footer', function () { cy.visit('/') cy.get('footer').findByRole('link', { name: 'Fork on GitHub!' }) }) }) - describe('custom settings', () => { + describe('custom settings', function () { startWith({ vars: { OVERLEAF_APP_NAME: 'CUSTOM APP NAME', @@ -23,16 +23,16 @@ describe('Customization', () => { }, }) - it('should display custom name', () => { + it('should display custom name', function () { cy.visit('/') cy.get('nav').findByText('CUSTOM APP NAME') }) - it('should display custom left footer', () => { + it('should display custom left footer', function () { cy.visit('/') cy.get('footer').findByText('CUSTOM LEFT FOOTER') }) - it('should display custom right footer', () => { + it('should display custom right footer', function () { cy.visit('/') cy.get('footer').findByText('CUSTOM RIGHT FOOTER') }) diff --git a/server-ce/test/cypress.config.ts b/server-ce/test/cypress.config.ts index 659349e5c8..e42ef383a7 100644 --- a/server-ce/test/cypress.config.ts +++ b/server-ce/test/cypress.config.ts @@ -53,7 +53,7 @@ export default defineConfig({ viewportWidth: 1024, e2e: { baseUrl: 'http://localhost', - setupNodeEvents(on, config) { + setupNodeEvents(on) { on('task', { readPdf, readFileInZip, diff --git a/server-ce/test/cypress/support/e2e.js b/server-ce/test/cypress/support/e2e.ts similarity index 100% rename from server-ce/test/cypress/support/e2e.js rename to server-ce/test/cypress/support/e2e.ts diff --git a/server-ce/test/editor.spec.ts b/server-ce/test/editor.spec.ts index 590eed0840..4be41f19ba 100644 --- a/server-ce/test/editor.spec.ts +++ b/server-ce/test/editor.spec.ts @@ -13,7 +13,7 @@ import { prepareWaitForNextCompileSlot } from './helpers/compile' const USER = 'user@example.com' const COLLABORATOR = 'collaborator@example.com' -describe('editor', () => { +describe('editor', function () { if (isExcludedBySharding('PRO_DEFAULT_1')) return startWith({ pro: true }) ensureUserExists({ email: USER }) @@ -23,7 +23,7 @@ describe('editor', () => { let projectId: string let recompile: () => void let waitForCompile: (fn: () => void) => void - beforeWithReRunOnTestRetry(function () { + beforeWithReRunOnTestRetry(() => { projectName = `project-${uuid()}` login(USER) createProject(projectName, { type: 'Example project', open: false }).then( @@ -32,7 +32,7 @@ describe('editor', () => { ;({ recompile, waitForCompile } = prepareWaitForNextCompileSlot()) }) - beforeEach(() => { + beforeEach(function () { login(USER) waitForCompile(() => { openProjectById(projectId) @@ -58,7 +58,7 @@ describe('editor', () => { changeSpellCheckLanguageTo('Off') }) - it('word dictionary and spelling', () => { + it('word dictionary and spelling', function () { changeSpellCheckLanguageTo('English (American)') createNewFile() const word = createRandomLetterString() @@ -109,8 +109,8 @@ describe('editor', () => { }) }) - describe('editor', () => { - it('renders jpg', () => { + describe('editor', function () { + it('renders jpg', function () { cy.findByRole('navigation', { name: 'Project files and outline', }) @@ -122,7 +122,7 @@ describe('editor', () => { .should('be.greaterThan', 0) }) - it('symbol palette', () => { + it('symbol palette', function () { createNewFile() cy.get('button[aria-label="Insert symbol"]').click({ @@ -139,20 +139,20 @@ describe('editor', () => { }) }) - describe('add new file to project', () => { - beforeEach(() => { + describe('add new file to project', function () { + beforeEach(function () { cy.findByRole('button', { name: 'New file' }).click() }) testNewFileUpload() - it('should not display import from URL', () => { + it('should not display import from URL', function () { cy.findByRole('button', { name: 'From external URL' }).should('not.exist') }) }) - describe('left menu', () => { - beforeEach(() => { + describe('left menu', function () { + beforeEach(function () { cy.findByRole('navigation', { name: 'Project actions', }) @@ -160,7 +160,7 @@ describe('editor', () => { .click() }) - it('can download project sources', () => { + it('can download project sources', function () { cy.findByRole('link', { name: 'Source' }).click() const zipName = projectName.replaceAll('-', '_') cy.task('readFileInZip', { @@ -169,7 +169,7 @@ describe('editor', () => { }).should('contain', 'Your introduction goes here') }) - it('can download project PDF', () => { + it('can download project PDF', function () { cy.log('ensure project is compiled') cy.findByRole('region', { name: 'PDF preview and logs' }).should( 'contain.text', @@ -185,7 +185,7 @@ describe('editor', () => { }) }) - it('word count', () => { + it('word count', function () { cy.log('ensure project is compiled') cy.findByRole('region', { name: 'PDF preview and logs' }).should( 'contain.text', @@ -206,8 +206,8 @@ describe('editor', () => { }) }) - describe('layout selector', () => { - it('show editor only and switch between editor and pdf', () => { + 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' ) @@ -238,7 +238,7 @@ describe('editor', () => { cy.get('.cm-editor').should('be.visible') }) - it('show PDF only and go back to Editor & PDF', () => { + it('show PDF only and go back to Editor & PDF', function () { cy.findByRole('region', { name: 'PDF preview and logs' }).should( 'be.visible' ) @@ -265,7 +265,7 @@ describe('editor', () => { cy.get('.cm-editor').should('be.visible') }) - it('PDF in a separate tab (tests editor only)', () => { + it('PDF in a separate tab (tests editor only)', function () { cy.findByTestId('pdf-viewer').should('be.visible') cy.get('.cm-editor').should('be.visible') diff --git a/server-ce/test/external-auth.spec.ts b/server-ce/test/external-auth.spec.ts index f26947e8a8..532ed91f20 100644 --- a/server-ce/test/external-auth.spec.ts +++ b/server-ce/test/external-auth.spec.ts @@ -1,7 +1,7 @@ import { isExcludedBySharding, startWith } from './helpers/config' import { createProject } from './helpers/project' -describe('SAML', () => { +describe('SAML', function () { if (isExcludedBySharding('PRO_CUSTOM_1')) return const samlURL = Cypress.env('SAML_URL') || 'http://saml' @@ -22,7 +22,7 @@ describe('SAML', () => { }, }) - it('login', () => { + it('login', function () { cy.visit('/') cy.findByText('Log in with SAML Test Server').click() @@ -39,7 +39,7 @@ describe('SAML', () => { }) }) -describe('LDAP', () => { +describe('LDAP', function () { if (isExcludedBySharding('PRO_CUSTOM_1')) return startWith({ pro: true, @@ -57,7 +57,7 @@ describe('LDAP', () => { }, }) - it('login', () => { + it('login', function () { cy.visit('/') cy.findByText('Log in LDAP') diff --git a/server-ce/test/filestore-migration.spec.ts b/server-ce/test/filestore-migration.spec.ts index 45a915a4c3..7d73c70418 100644 --- a/server-ce/test/filestore-migration.spec.ts +++ b/server-ce/test/filestore-migration.spec.ts @@ -295,7 +295,7 @@ describe('filestore migration', function () { // ------------------- // filestore-migration - beforeEach(() => { + beforeEach(function () { login(email) waitForCompile(() => { openProjectById(projectId) @@ -311,7 +311,7 @@ describe('filestore migration', function () { } }) - it('renders image of example project', () => { + it('renders image of example project', function () { cy.findByTestId('file-tree').findByText(defaultImage).click() cy.get(`[alt="${defaultImage}"]`) .should('be.visible') diff --git a/server-ce/test/git-bridge.spec.ts b/server-ce/test/git-bridge.spec.ts index 9b97c5538d..219feb92b0 100644 --- a/server-ce/test/git-bridge.spec.ts +++ b/server-ce/test/git-bridge.spec.ts @@ -65,7 +65,7 @@ describe('git-bridge', function () { login(USER) }) - it('should render the git-bridge UI in the settings', () => { + it('should render the git-bridge UI in the settings', function () { maybeClearAllTokens() cy.visit('/user/settings') cy.findByRole('heading', { name: 'Git integration' }) @@ -147,7 +147,7 @@ describe('git-bridge', function () { }) }) - describe('git access', () => { + describe('git access', function () { ensureUserExists({ email: 'collaborator-rw@example.com' }) ensureUserExists({ email: 'collaborator-ro@example.com' }) ensureUserExists({ email: 'collaborator-link-rw@example.com' }) @@ -156,13 +156,13 @@ describe('git-bridge', function () { let projectName: string let recompile: () => void let waitForCompile: (triggerCompile: () => void) => void - beforeEach(() => { + beforeEach(function () { projectName = uuid() 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', function () { maybeClearAllTokens() waitForCompile(() => { openProjectByName(projectName) @@ -170,7 +170,7 @@ describe('git-bridge', function () { checkGitAccess('readAndWrite') }) - it('should expose r/w interface to invited r/w collaborator', () => { + it('should expose r/w interface to invited r/w collaborator', function () { shareProjectByEmailAndAcceptInviteViaDash( projectName, 'collaborator-rw@example.com', @@ -183,7 +183,7 @@ describe('git-bridge', function () { checkGitAccess('readAndWrite') }) - it('should expose r/o interface to invited r/o collaborator', () => { + it('should expose r/o interface to invited r/o collaborator', function () { shareProjectByEmailAndAcceptInviteViaDash( projectName, 'collaborator-ro@example.com', @@ -196,7 +196,7 @@ describe('git-bridge', function () { checkGitAccess('readOnly') }) - it('should expose r/w interface to link-sharing r/w collaborator', () => { + 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' @@ -213,7 +213,7 @@ describe('git-bridge', function () { }) }) - it('should expose r/o interface to link-sharing r/o collaborator', () => { + it('should expose r/o interface to link-sharing r/o collaborator', function () { waitForCompile(() => { openProjectByName(projectName) }) @@ -267,7 +267,7 @@ describe('git-bridge', function () { const dir = `/${projectId}` async function readFile(path: string): Promise { - return new Promise((resolve, reject) => { + return await new Promise((resolve, reject) => { fs.readFile(path, { encoding: 'utf8' }, (err, blob) => { if (err) return reject(err) resolve(blob as string) @@ -276,7 +276,7 @@ describe('git-bridge', function () { } async function writeFile(path: string, data: string) { - return new Promise((resolve, reject) => { + return await new Promise((resolve, reject) => { fs.writeFile(path, data, undefined, err => { if (err) return reject(err) resolve() @@ -398,8 +398,9 @@ Hello world cy.findByText(/\\documentclass/) .parent() .parent() + .as('documentclass') .click() - .type('% via editor{enter}') + cy.get('@documentclass').type('% via editor{enter}') // Trigger flush via compile recompile() @@ -433,7 +434,7 @@ Hello world function checkDisabled() { ensureUserExists({ email: USER }) - it('should not render the git-bridge UI in the settings', () => { + 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') @@ -454,7 +455,7 @@ Hello world }) } - describe('disabled in Server Pro', () => { + describe('disabled in Server Pro', function () { if (isExcludedBySharding('PRO_DEFAULT_1')) return startWith({ pro: true, @@ -462,7 +463,7 @@ Hello world checkDisabled() }) - describe('unavailable in CE', () => { + describe('unavailable in CE', function () { if (isExcludedBySharding('CE_CUSTOM_1')) return startWith({ pro: false, diff --git a/server-ce/test/graceful-shutdown.spec.ts b/server-ce/test/graceful-shutdown.spec.ts index 014b3c978d..58fd2760f4 100644 --- a/server-ce/test/graceful-shutdown.spec.ts +++ b/server-ce/test/graceful-shutdown.spec.ts @@ -28,7 +28,7 @@ describe('GracefulShutdown', function () { ensureUserExists({ email: USER }) let projectId: string - it('should display banner and flush changes out of redis', () => { + it('should display banner and flush changes out of redis', function () { bringServerProBackUp() login(USER) const { recompile, waitForCompile } = prepareWaitForNextCompileSlot() diff --git a/server-ce/test/helpers/beforeWithReRunOnTestRetry.ts b/server-ce/test/helpers/beforeWithReRunOnTestRetry.ts index ce552c3759..0b67e3c910 100644 --- a/server-ce/test/helpers/beforeWithReRunOnTestRetry.ts +++ b/server-ce/test/helpers/beforeWithReRunOnTestRetry.ts @@ -1,6 +1,6 @@ export function beforeWithReRunOnTestRetry(fn: () => void | Promise) { let ranOnce = false - beforeEach(() => { + beforeEach(function () { if (ranOnce && Cypress.currentRetry === 0) return ranOnce = true return fn() diff --git a/server-ce/test/helpers/email.ts b/server-ce/test/helpers/email.ts index 036f746f65..ab5150a33f 100644 --- a/server-ce/test/helpers/email.ts +++ b/server-ce/test/helpers/email.ts @@ -7,7 +7,10 @@ */ export function openEmail( subject: string | RegExp, - runner: (frame: Cypress.Chainable>, args: T) => void, + runner: ( + frame: Cypress.Chainable>, + args: T + ) => void, args?: T ) { const runnerS = runner.toString() @@ -28,9 +31,11 @@ export function openEmail( // Use force as the subject is partially hidden cy.contains(subject).click({ force: true }) cy.log('wait for iframe loading') + // eslint-disable-next-line cypress/no-unnecessary-waiting cy.wait(1000) cy.get('iframe[id="messagecontframe"]').then(frame => { // runnerS='(frame, args) => { runner body }'. Extract the runnable function. + // eslint-disable-next-line no-new-func const runner = new Function('return ' + runnerS)() runner(cy.wrap(frame.prop('contentWindow').document.body), args) }) diff --git a/server-ce/test/helpers/hostAdminClient.ts b/server-ce/test/helpers/hostAdminClient.ts index 598ca3dc30..5934fa1fca 100644 --- a/server-ce/test/helpers/hostAdminClient.ts +++ b/server-ce/test/helpers/hostAdminClient.ts @@ -130,7 +130,7 @@ export async function purgeFilestoreData() { } async function sleep(ms: number) { - return new Promise(resolve => { + return await new Promise(resolve => { setTimeout(resolve, ms) }) } diff --git a/server-ce/test/helpers/project.ts b/server-ce/test/helpers/project.ts index e252649d67..8fd635c921 100644 --- a/server-ce/test/helpers/project.ts +++ b/server-ce/test/helpers/project.ts @@ -114,11 +114,10 @@ function shareProjectByEmail( cy.findByLabelText('Add email address', { selector: 'input' }) .parents('form') .within(() => { - cy.findByTestId('add-collaborator-select') - .click() - .then(() => { - cy.findByRole('option', { name: level }).click() - }) + cy.findByTestId('add-collaborator-select').as('select').click() + cy.get('@select').then(() => { + cy.findByRole('option', { name: level }).click() + }) }) cy.findByRole('button', { name: 'Invite' }).click() cy.findByText('Invite not yet accepted.') @@ -275,12 +274,12 @@ export function prepareFileUploadTest(binary = false) { } export function testNewFileUpload() { - it('can upload text file', () => { + it('can upload text file', function () { const check = prepareFileUploadTest(false) check() }) - it('can upload binary file', () => { + it('can upload binary file', function () { const check = prepareFileUploadTest(true) check() }) diff --git a/server-ce/test/helpers/read-file.ts b/server-ce/test/helpers/read-file.ts index aec7bb89b1..a1c5a1260e 100644 --- a/server-ce/test/helpers/read-file.ts +++ b/server-ce/test/helpers/read-file.ts @@ -1,9 +1,8 @@ -import fs from 'fs' -import path from 'path' -// @ts-ignore broken package entrypoint -import pdf from 'pdf-parse/lib/pdf-parse.js' +import fs from 'node:fs' +import path from 'node:path' +import { PDFParse } from 'pdf-parse' import AdmZip from 'adm-zip' -import { setTimeout } from 'timers/promises' +import { setTimeout } from 'node:timers/promises' const MAX_ATTEMPTS = 15 const POLL_INTERVAL = 500 @@ -23,7 +22,7 @@ export async function readFileInZip({ const zip = new AdmZip(path.resolve(pathToZip)) const entry = zip .getEntries() - .find(entry => entry.entryName == fileToRead) + .find(entry => entry.entryName === fileToRead) if (entry) { return entry.getData().toString('utf8') } else { @@ -41,8 +40,15 @@ export async function readPdf(file: string) { while (attempt < MAX_ATTEMPTS) { if (fs.existsSync(file)) { const dataBuffer = fs.readFileSync(path.resolve(file)) - const { text } = await pdf(dataBuffer) - return text + const parser = new PDFParse({ data: dataBuffer }) + try { + const result = await parser.getText() + return result.text + } catch (error) { + console.error('PDF parsing failed:', error) + } finally { + await parser.destroy() + } } await setTimeout(POLL_INTERVAL) attempt++ diff --git a/server-ce/test/history.spec.ts b/server-ce/test/history.spec.ts index 3b4b253b8a..7f146c758e 100644 --- a/server-ce/test/history.spec.ts +++ b/server-ce/test/history.spec.ts @@ -61,7 +61,7 @@ describe('History', function () { const CLASS_ADDITION = 'ol-cm-addition-marker' const CLASS_DELETION = 'ol-cm-deletion-marker' - it('should support labels, comparison and download', () => { + it('should support labels, comparison and download', function () { const { recompile, waitForCompile } = prepareWaitForNextCompileSlot() waitForCompile(() => { createProject('labels') diff --git a/server-ce/test/learn-wiki.spec.ts b/server-ce/test/learn-wiki.spec.ts index 2070868a85..cea43ed4cf 100644 --- a/server-ce/test/learn-wiki.spec.ts +++ b/server-ce/test/learn-wiki.spec.ts @@ -1,6 +1,5 @@ import { isExcludedBySharding, startWith } from './helpers/config' import { ensureUserExists, login } from './helpers/login' -import { v4 as uuid } from 'uuid' describe('LearnWiki', function () { const COPYING_A_PROJECT_URL = '/learn/how-to/Copying_a_project' @@ -15,7 +14,7 @@ describe('LearnWiki', function () { ensureUserExists({ email: WITHOUT_PROJECTS_USER }) ensureUserExists({ email: REGULAR_USER }) - describe('enabled in Pro', () => { + describe('enabled in Pro', function () { if (isExcludedBySharding('PRO_CUSTOM_2')) return startWith({ pro: true, @@ -24,7 +23,7 @@ describe('LearnWiki', function () { }, }) - it('should add a documentation entry to the nav bar', () => { + it('should add a documentation entry to the nav bar', function () { login(REGULAR_USER) cy.visit('/project') cy.findByRole('menuitem', { name: 'Documentation' }).should( @@ -34,7 +33,7 @@ describe('LearnWiki', function () { ) }) - it('should display a tutorial link in the welcome page', () => { + it('should display a tutorial link in the welcome page', function () { login(WITHOUT_PROJECTS_USER) cy.visit('/project') cy.findByRole('link', { name: LABEL_LEARN_LATEX }) @@ -45,7 +44,7 @@ describe('LearnWiki', function () { }) }) - it('should render wiki page', () => { + it('should render wiki page', function () { login(REGULAR_USER) cy.visit(UPLOADING_A_PROJECT_URL) @@ -81,13 +80,13 @@ describe('LearnWiki', function () { }) }) - describe('disabled in Pro', () => { + describe('disabled in Pro', function () { if (isExcludedBySharding('PRO_DEFAULT_1')) return startWith({ pro: true }) checkDisabled() }) - describe('unavailable in CE', () => { + describe('unavailable in CE', function () { if (isExcludedBySharding('CE_CUSTOM_1')) return startWith({ pro: false, @@ -100,13 +99,13 @@ describe('LearnWiki', function () { }) function checkDisabled() { - it('should not add a documentation entry to the nav bar', () => { + it('should not add a documentation entry to the nav bar', function () { login(REGULAR_USER) cy.visit('/project') cy.findByText('Documentation').should('not.exist') }) - it('should not render wiki page', () => { + it('should not render wiki page', function () { login(REGULAR_USER) cy.visit(COPYING_A_PROJECT_URL, { failOnStatusCode: false, @@ -114,7 +113,7 @@ describe('LearnWiki', function () { cy.findByText('Not found') }) - it('should not display a tutorial link in the welcome page', () => { + it('should not display a tutorial link in the welcome page', function () { login(WITHOUT_PROJECTS_USER) cy.visit('/project') cy.findByText(LABEL_LEARN_LATEX).should('not.exist') diff --git a/server-ce/test/package.json b/server-ce/test/package.json index 0736ce199e..982b3d09e8 100644 --- a/server-ce/test/package.json +++ b/server-ce/test/package.json @@ -7,14 +7,15 @@ "cypress:open": "cypress open --e2e --browser chrome", "cypress:run": "cypress run --e2e --browser chrome", "format": "prettier --list-different $PWD/'**/*.{js,mjs,ts,tsx}'", - "format:fix": "prettier --write $PWD/'**/*.{js,mjs,ts,tsx}'" + "format:fix": "prettier --write $PWD/'**/*.{js,mjs,ts,tsx}'", + "lint": "eslint --max-warnings 0 --format unix --ext .js,.jsx,.mjs,.ts,.tsx .", + "lint:fix": "eslint --fix --ext .js,.jsx,.mjs,.ts,.tsx ." }, "devDependencies": { "@isomorphic-git/lightning-fs": "^4.6.0", "@overleaf/validation-tools": "*", "@testing-library/cypress": "^10.0.3", "@types/adm-zip": "^0.5.7", - "@types/pdf-parse": "^1.1.5", "@types/uuid": "^9.0.8", "adm-zip": "^0.5.12", "body-parser": "^1.20.3", @@ -24,7 +25,7 @@ "isomorphic-git": "^1.33.1", "js-yaml": "^4.1.1", "mocha-junit-reporter": "^2.2.1", - "pdf-parse": "^1.1.1", + "pdf-parse": "^2.3.0", "uuid": "^9.0.1", "zod-validation-error": "^4.0.1" } diff --git a/server-ce/test/project-list.spec.ts b/server-ce/test/project-list.spec.ts index 443a3c41af..c4ee3cf238 100644 --- a/server-ce/test/project-list.spec.ts +++ b/server-ce/test/project-list.spec.ts @@ -6,7 +6,7 @@ import { v4 as uuid } from 'uuid' const WITHOUT_PROJECTS_USER = 'user-without-projects@example.com' const REGULAR_USER = 'user@example.com' -describe('Project List', () => { +describe('Project List', function () { if (isExcludedBySharding('PRO_DEFAULT_2')) return startWith({ pro: true }) @@ -15,10 +15,10 @@ describe('Project List', () => { return cy.findByText(projectName).parent().parent() } - describe('user with no projects', () => { + describe('user with no projects', function () { ensureUserExists({ email: WITHOUT_PROJECTS_USER }) - it("'Import from GitHub' is not displayed in the welcome page", () => { + it("'Import from GitHub' is not displayed in the welcome page", function () { login(WITHOUT_PROJECTS_USER) cy.visit('/project') cy.findByRole('button', { name: 'Create a new project' }).click() @@ -28,11 +28,11 @@ describe('Project List', () => { }) }) - describe('user with projects', () => { + describe('user with projects', function () { const projectName = `test-project-${uuid()}` ensureUserExists({ email: REGULAR_USER }) - before(() => { + before(function () { login(REGULAR_USER) createProject(projectName, { type: 'Example project', open: false }) }) @@ -41,7 +41,7 @@ describe('Project List', () => { cy.visit('/project') }) - it('Can download project sources', () => { + it('Can download project sources', function () { findProjectRow(projectName).within(() => cy.findByRole('button', { name: 'Download .zip file' }).click() ) @@ -53,7 +53,7 @@ describe('Project List', () => { }).should('contain', 'Your introduction goes here') }) - it('Can download project PDF', () => { + it('Can download project PDF', function () { findProjectRow(projectName).within(() => cy.findByRole('button', { name: 'Download PDF' }).click() ) @@ -65,7 +65,7 @@ describe('Project List', () => { ) }) - it('can assign and remove tags to projects', () => { + it('can assign and remove tags to projects', function () { const tagName = uuid().slice(0, 7) // long tag names are truncated in the UI, which affects selectors cy.log('select project') cy.findByRole('checkbox', { name: `Select ${projectName}` }).check() @@ -88,7 +88,7 @@ describe('Project List', () => { ) }) - it('can filter by tag', () => { + it('can filter by tag', function () { cy.log('create a separate project to filter') const nonTaggedProjectName = `project-${uuid()}` createProject(nonTaggedProjectName, { open: false }) diff --git a/server-ce/test/project-sharing.spec.ts b/server-ce/test/project-sharing.spec.ts index e9cb8e5e23..e4a40fb59d 100644 --- a/server-ce/test/project-sharing.spec.ts +++ b/server-ce/test/project-sharing.spec.ts @@ -27,13 +27,13 @@ describe('Project Sharing', function () { let projectName: string let recompile: () => void let waitForCompile: (triggerCompile: () => void) => void - beforeWithReRunOnTestRetry(function () { + beforeWithReRunOnTestRetry(() => { projectName = getSpamSafeProjectName() ;({ recompile, waitForCompile } = prepareWaitForNextCompileSlot()) setupTestProject() }) - beforeEach(() => { + beforeEach(function () { // Always start with a fresh session cy.session([uuid()], () => {}) }) @@ -245,22 +245,22 @@ describe('Project Sharing', function () { shareProjectByEmailAndAcceptInviteViaEmail(projectName, email, 'Viewer') }) - it('should grant the collaborator read access', () => { + it('should grant the collaborator read access', function () { expectFullReadOnlyAccess() expectProjectDashboardEntry() }) }) - describe('read only', () => { + describe('read only', function () { const email = 'collaborator-ro@example.com' ensureUserExists({ email }) - beforeWithReRunOnTestRetry(function () { + beforeWithReRunOnTestRetry(() => { login('user@example.com') shareProjectByEmailAndAcceptInviteViaDash(projectName, email, 'Viewer') }) - it('should grant the collaborator read access', () => { + it('should grant the collaborator read access', function () { login(email) openProjectByName(projectName) expectFullReadOnlyAccess() @@ -268,16 +268,16 @@ describe('Project Sharing', function () { }) }) - describe('read and write', () => { + describe('read and write', function () { const email = 'collaborator-rw@example.com' ensureUserExists({ email }) - beforeWithReRunOnTestRetry(function () { + beforeWithReRunOnTestRetry(() => { login('user@example.com') shareProjectByEmailAndAcceptInviteViaDash(projectName, email, 'Editor') }) - it('should grant the collaborator write access', () => { + it('should grant the collaborator write access', function () { login(email) openProjectByName(projectName) expectFullReadAndWriteAccess() @@ -286,13 +286,13 @@ describe('Project Sharing', function () { }) }) - describe('token access', () => { - describe('logged in', () => { - describe('read only', () => { + 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', () => { + it('should grant restricted read access', function () { login(email) openProjectViaLinkSharingAsUser( linkSharingReadOnly, @@ -304,11 +304,11 @@ describe('Project Sharing', function () { }) }) - describe('read and write', () => { + describe('read and write', function () { const email = 'collaborator-link-rw@example.com' ensureUserExists({ email }) - it('should grant full write access', () => { + it('should grant full write access', function () { login(email) openProjectViaLinkSharingAsUser( linkSharingReadAndWrite, @@ -322,8 +322,8 @@ describe('Project Sharing', function () { }) }) - describe('with OVERLEAF_ALLOW_PUBLIC_ACCESS=false', () => { - describe('wrap startup', () => { + describe('with OVERLEAF_ALLOW_PUBLIC_ACCESS=false', function () { + describe('wrap startup', function () { startWith({ pro: true, vars: { @@ -331,12 +331,12 @@ describe('Project Sharing', function () { }, withDataDir: true, }) - it('should block access', () => { + it('should block access', function () { expectNoAccess() }) }) - describe('with OVERLEAF_ALLOW_ANONYMOUS_READ_AND_WRITE_SHARING=true', () => { + describe('with OVERLEAF_ALLOW_ANONYMOUS_READ_AND_WRITE_SHARING=true', function () { startWith({ pro: true, vars: { @@ -345,14 +345,14 @@ describe('Project Sharing', function () { }, withDataDir: true, }) - it('should block access', () => { + it('should block access', function () { expectNoAccess() }) }) }) - describe('with OVERLEAF_ALLOW_PUBLIC_ACCESS=true', () => { - describe('wrap startup', () => { + describe('with OVERLEAF_ALLOW_PUBLIC_ACCESS=true', function () { + describe('wrap startup', function () { startWith({ pro: true, vars: { @@ -360,18 +360,18 @@ describe('Project Sharing', function () { }, withDataDir: true, }) - it('should grant read access with read link', () => { + it('should grant read access with read link', function () { openProjectViaLinkSharingAsAnon(linkSharingReadOnly) expectRestrictedReadOnlyAccess() }) - it('should prompt for login with write link', () => { + 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', () => { + describe('with OVERLEAF_ALLOW_ANONYMOUS_READ_AND_WRITE_SHARING=true', function () { startWith({ pro: true, vars: { @@ -381,12 +381,12 @@ describe('Project Sharing', function () { withDataDir: true, }) - it('should grant read access with read link', () => { + it('should grant read access with read link', function () { openProjectViaLinkSharingAsAnon(linkSharingReadOnly) expectRestrictedReadOnlyAccess() }) - it('should grant write access with write link', () => { + it('should grant write access with write link', function () { openProjectViaLinkSharingAsAnon(linkSharingReadAndWrite) expectAnonymousReadAndWriteAccess() expectEditAuthoredAs('Anonymous') @@ -394,7 +394,7 @@ describe('Project Sharing', function () { }) }) - describe('with OVERLEAF_DISABLE_LINK_SHARING=true', () => { + describe('with OVERLEAF_DISABLE_LINK_SHARING=true', function () { const email = 'collaborator-email@example.com' ensureUserExists({ email }) @@ -448,14 +448,14 @@ describe('Project Sharing', function () { ) }) - it('should not display link sharing in the sharing modal', () => { + it('should not display link sharing in the sharing modal', function () { login('user@example.com') openProjectByName(projectName) cy.findByText('Share').click() cy.findByText('Turn on link sharing').should('not.exist') }) - it('should block new access to read-only link shared projects', () => { + it('should block new access to read-only link shared projects', function () { login(email) // Test read-only link returns 404 @@ -467,7 +467,7 @@ describe('Project Sharing', function () { }) }) - it('should block new access to read-write link shared projects', () => { + it('should block new access to read-write link shared projects', function () { login(email) // Test read-write link returns 404 @@ -479,7 +479,7 @@ describe('Project Sharing', function () { }) }) - it('should continue to allow email sharing', () => { + it('should continue to allow email sharing', function () { login('user@example.com') shareProjectByEmailAndAcceptInviteViaEmail( projectName, @@ -490,14 +490,14 @@ describe('Project Sharing', function () { expectProjectDashboardEntry() }) - it('should retain read-only access when project was joined via link before link sharing was turned off', () => { + 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', () => { + it('should retain read-write access when project was joined via link before link sharing was turned off', function () { login(retainedEditorEmail) openProjectByName(projectName) expectFullReadAndWriteAccess() diff --git a/server-ce/test/sandboxed-compiles.spec.ts b/server-ce/test/sandboxed-compiles.spec.ts index 7e7ebac7fb..872565ff49 100644 --- a/server-ce/test/sandboxed-compiles.spec.ts +++ b/server-ce/test/sandboxed-compiles.spec.ts @@ -156,6 +156,7 @@ describe('SandboxedCompiles', function () { // 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)', @@ -309,6 +310,7 @@ describe('SandboxedCompiles', function () { }) // 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 }) diff --git a/server-ce/test/templates.spec.ts b/server-ce/test/templates.spec.ts index 2e61c67954..8aba716af2 100644 --- a/server-ce/test/templates.spec.ts +++ b/server-ce/test/templates.spec.ts @@ -10,7 +10,7 @@ const TEMPLATES_USER = 'templates@example.com' // Re-use value for "exists" and "does not exist" tests const LABEL_BROWSE_TEMPLATES = 'Browse templates' -describe('Templates', () => { +describe('Templates', function () { ensureUserExists({ email: TEMPLATES_USER }) ensureUserExists({ email: WITHOUT_PROJECTS_USER }) @@ -31,7 +31,7 @@ describe('Templates', () => { } } - describe('enabled in Server Pro', () => { + describe('enabled in Server Pro', function () { if (isExcludedBySharding('PRO_CUSTOM_2')) return startWith({ pro: true, @@ -40,7 +40,7 @@ describe('Templates', () => { ensureUserExists({ email: REGULAR_USER }) ensureUserExists({ email: ADMIN_USER, isAdmin: true }) - it('should show templates link on welcome page', () => { + it('should show templates link on welcome page', function () { login(WITHOUT_PROJECTS_USER) cy.visit('/') cy.findByRole('link', { name: LABEL_BROWSE_TEMPLATES }) @@ -49,7 +49,7 @@ describe('Templates', () => { cy.url().should('match', /\/templates$/) }) - it('should have templates feature', () => { + it('should have templates feature', function () { login(TEMPLATES_USER) const name = `Template ${Date.now()}` const description = `Template Description ${Date.now()}` @@ -64,11 +64,8 @@ describe('Templates', () => { .click() cy.findByText('Manage Template').click() - cy.findByText('Template Description') - .click() - .parent() - .get('textarea') - .type(description) + cy.findByText('Template Description').as('description').click() + cy.get('@description').parent().get('textarea').type(description) cy.findByText('Publish').click() cy.findByText('Publishing…').parent().should('be.disabled') cy.findByText('Publish').should('not.exist') @@ -229,7 +226,7 @@ describe('Templates', () => { }) function checkDisabled() { - it('should not have templates feature', () => { + it('should not have templates feature', function () { login(TEMPLATES_USER) cy.visit('/') @@ -254,7 +251,7 @@ describe('Templates', () => { cy.findAllByText('All Templates').should('not.exist') }) - it('should not show templates link on welcome page', () => { + it('should not show templates link on welcome page', function () { login(WITHOUT_PROJECTS_USER) cy.visit('/') cy.findByText(NEW_PROJECT_BUTTON_MATCHER) // wait for lazy loading @@ -262,13 +259,13 @@ describe('Templates', () => { }) } - describe('disabled Server Pro', () => { + describe('disabled Server Pro', function () { if (isExcludedBySharding('PRO_DEFAULT_2')) return startWith({ pro: true }) checkDisabled() }) - describe('unavailable in CE', () => { + describe('unavailable in CE', function () { if (isExcludedBySharding('CE_CUSTOM_1')) return startWith({ pro: false, diff --git a/server-ce/test/tsconfig.json b/server-ce/test/tsconfig.json index 8d72981ec9..db40ae1adf 100644 --- a/server-ce/test/tsconfig.json +++ b/server-ce/test/tsconfig.json @@ -11,7 +11,8 @@ "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */, "skipLibCheck": true /* Skip type checking of declaration files. */, "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */, - "types": ["cypress", "node", "@testing-library/cypress"] + "types": ["cypress", "node", "@testing-library/cypress"], + "allowImportingTsExtensions": true }, - "include": ["**/*.ts", "**/*.tsx"] + "include": ["**/*.ts", "**/*.js"] } diff --git a/server-ce/test/upgrading.spec.ts b/server-ce/test/upgrading.spec.ts index fe9f4c3a46..333b8c82a2 100644 --- a/server-ce/test/upgrading.spec.ts +++ b/server-ce/test/upgrading.spec.ts @@ -24,7 +24,7 @@ describe('Upgrading', function () { ) { const startOptions = steps.shift()! - before(async () => { + before(async function () { cy.log('Create old instance') }) startWith({ @@ -38,7 +38,7 @@ describe('Upgrading', function () { cy.log('Create initial user after deleting it') }) ensureUserExists({ email: USER }) - before(() => { + before(function () { cy.log('Populate old instance') login(USER) ;({ recompile, waitForCompile } = prepareWaitForNextCompileSlot()) @@ -76,12 +76,12 @@ describe('Upgrading', function () { } cy.findByText('History').click() for (let i = 0; i < 3; i++) { - cy.findByText(new RegExp(`\\\\section\{Old Section ${i}}`)) + cy.findByText(new RegExp(`\\\\section{Old Section ${i}}`)) } }) for (const step of steps) { - before(() => { + before(function () { cy.log(`Upgrade to version ${step.version}`) // Navigate way from editor to avoid redirect to /login when the next instance comes up (which slows down tests) @@ -113,16 +113,16 @@ describe('Upgrading', function () { step.hook?.() } - beforeEach(() => { + beforeEach(function () { login(USER) }) - it('should list the old project', () => { + it('should list the old project', function () { cy.visit('/project') cy.findByText(PROJECT_NAME) }) - it('should open the old project', () => { + it('should open the old project', function () { waitForCompile(() => { openProjectByName(PROJECT_NAME) }) @@ -174,21 +174,21 @@ describe('Upgrading', function () { }) }, } - describe('from 4.2 to latest', () => { + describe('from 4.2 to latest', function () { testUpgrade([ optionsFourDotTwo, optionsBinaryFilesMigration, { version: 'latest' }, ]) }) - describe('from 5.0 to latest', () => { + describe('from 5.0 to latest', function () { testUpgrade([ { version: '5.0' }, optionsBinaryFilesMigration, { version: 'latest' }, ]) }) - describe('doc version recovery', () => { + describe('doc version recovery', function () { testUpgrade([ optionsFourDotTwo, {