diff --git a/src/node/eejs/index.ts b/src/node/eejs/index.ts index 783cfa443..671b90294 100644 --- a/src/node/eejs/index.ts +++ b/src/node/eejs/index.ts @@ -24,11 +24,13 @@ import ejs from 'ejs'; import fs from 'fs'; import hooks from '../../static/js/pluginfw/hooks.js'; +import * as i18n from '../hooks/i18n.js'; import path from 'node:path'; // @ts-ignore import resolve from 'resolve'; import settings from '../utils/Settings.js'; import { pluginInstallPath } from '../../static/js/pluginfw/installer.js'; +import pluginUtils from '../../static/js/pluginfw/shared.js'; import { fileURLToPath } from 'node:url'; import { dirname } from 'node:path'; import { createRequire } from 'node:module'; @@ -36,6 +38,10 @@ import { createRequire } from 'node:module'; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); const requireFromHere = createRequire(import.meta.url); +const templateModules = new Map([ + ['ep_etherpad-lite/node/hooks/i18n', i18n], + ['ep_etherpad-lite/static/js/pluginfw/shared', pluginUtils], +]); const templateCache = new Map(); @@ -111,7 +117,7 @@ eejs.require = ( const ejspath = resolve.sync(name, { paths, basedir, extensions: ['.html', '.ejs'] }); args.e = eejs; - args.require = requireFromHere; + args.require = (name: string) => templateModules.get(name) ?? requireFromHere(name); const cache = settings.maxAge !== 0; const template = diff --git a/src/node/handler/ImportHandler.ts b/src/node/handler/ImportHandler.ts index 029a7a8b7..0ca5c20e7 100644 --- a/src/node/handler/ImportHandler.ts +++ b/src/node/handler/ImportHandler.ts @@ -75,7 +75,7 @@ const tmpDirectory = os.tmpdir(); * @param {String} padId the pad id to export * @param {String} authorId the author id to use for the import */ -const doImport = async (req:any, res:any, padId:string, authorId:string) => { +const performImport = async (req:any, res:any, padId:string, authorId:string) => { // pipe to a file // convert file to html via soffice // set html in the pad @@ -248,7 +248,7 @@ export const doImport = async (req:any, res:any, padId:string, authorId:string = let message = 'ok'; let directDatabaseAccess; try { - directDatabaseAccess = await doImport(req, res, padId, authorId); + directDatabaseAccess = await performImport(req, res, padId, authorId); } catch (err:any) { const known = err instanceof ImportError && err.status; if (!known) logger.error(`Internal error during import: ${err.stack || err}`); diff --git a/src/node/handler/RestAPI.ts b/src/node/handler/RestAPI.ts index dae7271d5..24a3ec4e2 100644 --- a/src/node/handler/RestAPI.ts +++ b/src/node/handler/RestAPI.ts @@ -1,7 +1,7 @@ -import {ArgsExpressType} from "../types/ArgsExpressType.js"; -import {MapArrayType} from "../types/MapType.js"; +import type {ArgsExpressType} from "../types/ArgsExpressType.js"; +import type {MapArrayType} from "../types/MapType.js"; import {IncomingForm} from "formidable"; -import {ErrorCaused} from "../types/ErrorCaused.js"; +import type {ErrorCaused} from "../types/ErrorCaused.js"; import createHTTPError from "http-errors"; import * as apiHandler from './APIHandler.js'; diff --git a/src/node/hooks/express/admin.ts b/src/node/hooks/express/admin.ts index 6af8eb6e4..948bcfc59 100644 --- a/src/node/hooks/express/admin.ts +++ b/src/node/hooks/express/admin.ts @@ -1,8 +1,8 @@ 'use strict'; -import {ArgsExpressType} from "../../types/ArgsExpressType.js"; +import type {ArgsExpressType} from "../../types/ArgsExpressType.js"; import path from "path"; import fs from "fs"; -import {MapArrayType} from "../../types/MapType.js"; +import type {MapArrayType} from "../../types/MapType.js"; import settings from '../../utils/Settings.js'; diff --git a/src/node/hooks/express/adminplugins.ts b/src/node/hooks/express/adminplugins.ts index a6bea4f6e..65a4fd37b 100644 --- a/src/node/hooks/express/adminplugins.ts +++ b/src/node/hooks/express/adminplugins.ts @@ -1,14 +1,14 @@ 'use strict'; -import {ArgsExpressType} from "../../types/ArgsExpressType.js"; -import {ErrorCaused} from "../../types/ErrorCaused.js"; -import {QueryType} from "../../types/QueryType.js"; +import type {ArgsExpressType} from "../../types/ArgsExpressType.js"; +import type {ErrorCaused} from "../../types/ErrorCaused.js"; +import type {QueryType} from "../../types/QueryType.js"; import {getAvailablePlugins, install, search, uninstall} from "../../../static/js/pluginfw/installer.js"; -import {PackageData, PackageInfo} from "../../types/PackageInfo.js"; +import type {PackageData, PackageInfo} from "../../types/PackageInfo.js"; import semver from 'semver'; import log4js from 'log4js'; -import {MapArrayType} from "../../types/MapType.js"; +import type {MapArrayType} from "../../types/MapType.js"; import pluginDefs from '../../../static/js/pluginfw/plugin_defs.js'; import stats from '../../stats.js'; diff --git a/src/node/hooks/express/importexport.ts b/src/node/hooks/express/importexport.ts index 383e7e74b..713618a67 100644 --- a/src/node/hooks/express/importexport.ts +++ b/src/node/hooks/express/importexport.ts @@ -1,6 +1,6 @@ 'use strict'; -import {ArgsExpressType} from "../../types/ArgsExpressType"; +import type {ArgsExpressType} from "../../types/ArgsExpressType.js"; import hasPadAccess from '../../padaccess.js'; import settings, {exportAvailable} from '../../utils/Settings.js'; diff --git a/src/node/hooks/express/openapi.ts b/src/node/hooks/express/openapi.ts index a80218377..15186928b 100644 --- a/src/node/hooks/express/openapi.ts +++ b/src/node/hooks/express/openapi.ts @@ -1,8 +1,8 @@ 'use strict'; -import {OpenAPIOperations, OpenAPISuccessResponse, SwaggerUIResource} from "../../types/SwaggerUIResource"; -import {MapArrayType} from "../../types/MapType"; -import {ErrorCaused} from "../../types/ErrorCaused"; +import type {OpenAPIOperations, OpenAPISuccessResponse, SwaggerUIResource} from "../../types/SwaggerUIResource.ts"; +import type {MapArrayType} from "../../types/MapType.js"; +import type {ErrorCaused} from "../../types/ErrorCaused.js"; /** * node/hooks/express/openapi.js diff --git a/src/node/hooks/express/socketio.ts b/src/node/hooks/express/socketio.ts index 1f7a29386..95fa364a5 100644 --- a/src/node/hooks/express/socketio.ts +++ b/src/node/hooks/express/socketio.ts @@ -1,6 +1,6 @@ 'use strict'; -import {ArgsExpressType} from "../../types/ArgsExpressType"; +import type {ArgsExpressType} from "../../types/ArgsExpressType.js"; import events from 'events'; import * as express from '../express.js'; diff --git a/src/static/js/ace2_common.ts b/src/static/js/ace2_common.ts index 0a5f308e6..a5685f6ec 100644 --- a/src/static/js/ace2_common.ts +++ b/src/static/js/ace2_common.ts @@ -6,7 +6,7 @@ * TL;DR COMMENTS ON THIS FILE ARE HIGHLY APPRECIATED */ -import {MapArrayType} from "../../node/types/MapType"; +import type {MapArrayType} from "../../node/types/MapType.js"; /** * Copyright 2009 Google Inc. @@ -63,3 +63,12 @@ export const binarySearchInfinite = (expectedLength: number, func: (num: number) }; export const noop = () => {}; + +export default { + isNodeText, + getAssoc, + setAssoc, + binarySearch, + binarySearchInfinite, + noop, +}; diff --git a/src/static/js/ace2_inner.ts b/src/static/js/ace2_inner.ts index 1165ee544..74bf7fb8a 100644 --- a/src/static/js/ace2_inner.ts +++ b/src/static/js/ace2_inner.ts @@ -3736,3 +3736,7 @@ export const init = async (editorInfo, cssManagers) => { const editor = new Ace2Inner(editorInfo, cssManagers); await editor.init(); }; + +export default { + init, +}; diff --git a/src/static/js/pad.ts b/src/static/js/pad.ts index 9b277d0ce..846e67fce 100644 --- a/src/static/js/pad.ts +++ b/src/static/js/pad.ts @@ -956,5 +956,4 @@ export {settings}; export {randomString}; export {getParams}; export {pad}; -export {init}; export {baseURL}; diff --git a/src/static/js/pad_automatic_reconnect.ts b/src/static/js/pad_automatic_reconnect.ts index f63912cd2..deab32e5e 100644 --- a/src/static/js/pad_automatic_reconnect.ts +++ b/src/static/js/pad_automatic_reconnect.ts @@ -194,3 +194,7 @@ CountDownTimer.parse = (seconds) => ({ minutes: (seconds / 60) | 0, seconds: (seconds % 60) | 0, }); + +export default { + showCountDownTimerToReconnectOnModal, +}; diff --git a/src/static/js/pad_savedrevs.ts b/src/static/js/pad_savedrevs.ts index ddc14c48f..8d3f80535 100644 --- a/src/static/js/pad_savedrevs.ts +++ b/src/static/js/pad_savedrevs.ts @@ -37,3 +37,8 @@ export const saveNow = () => { export const init = (_pad) => { pad = _pad; }; + +export default { + saveNow, + init, +}; diff --git a/src/static/js/pluginfw/plugins.ts b/src/static/js/pluginfw/plugins.ts index 5549ea646..826ef8c86 100644 --- a/src/static/js/pluginfw/plugins.ts +++ b/src/static/js/pluginfw/plugins.ts @@ -1,7 +1,6 @@ // @ts-nocheck 'use strict'; - -import {createRequire} from 'node:module'; +import {pathToFileURL} from 'node:url'; import {promises as fs} from 'fs'; import log4js from 'log4js'; import path from 'path'; @@ -14,11 +13,6 @@ import settings, { getEpVersion, } from '../../../node/utils/Settings.js'; -// `installer.ts` is loaded lazily inside `getPackages()` to avoid an import cycle. Use a -// `createRequire`-backed `require` so the existing CommonJS-style lazy access keeps working in -// ESM. -const requireFromHere = createRequire(import.meta.url); - const logger = log4js.getLogger('plugins'); // Log the version of npm at startup. @@ -102,11 +96,88 @@ export const pathNormalization = (part, hookFnName, hookName) => { // If there is a single colon assume it's 'filename:funcname' not 'C:\\filename'. const functionName = (tmp.length > 1 ? tmp.pop() : null) || hookName; const moduleName = tmp.join(':') || part.plugin; - const packageDir = path.dirname(defs.plugins[part.plugin].package.path); - const fileName = path.join(packageDir, moduleName); + const pkg = defs.plugins[part.plugin].package; + const packageRoot = pkg.realPath || pkg.path; + const pluginPrefix = `${part.plugin}/`; + const relativeModuleName = moduleName.startsWith(pluginPrefix) + ? moduleName.slice(pluginPrefix.length) + : moduleName; + const fileName = path.isAbsolute(relativeModuleName) + ? relativeModuleName + : path.join(packageRoot, relativeModuleName); return `${fileName}:${functionName}`; }; +const loadServerHook = async (hookFnName, hookName) => { + const parts = hookFnName.split(':'); + let functionName; + let modulePath; + + if (parts[0].length === 1) { + if (parts.length === 3) functionName = parts.pop(); + modulePath = parts.join(':'); + } else { + modulePath = parts[0]; + functionName = parts[1]; + } + + functionName = functionName || hookName; + const candidates = path.extname(modulePath) === '' + ? [`${modulePath}.ts`, `${modulePath}.js`, modulePath] + : [modulePath]; + + let mod; + let lastErr; + for (const candidate of candidates) { + try { + mod = await import(pathToFileURL(candidate).href); + break; + } catch (err) { + lastErr = err; + } + } + if (mod == null) throw lastErr; + + for (const namespace of [mod, mod.default].filter((ns) => ns != null)) { + let hookFn = namespace; + let missing = false; + for (const name of functionName.split('.')) { + if (hookFn == null || !(name in hookFn)) { + missing = true; + break; + } + hookFn = hookFn[name]; + } + if (!missing) return hookFn; + } + return undefined; +}; + +const extractServerHooks = async (parts) => { + const hooksByName = {}; + for (const part of parts) { + for (const [hookName, regHookFnName] of Object.entries(part.hooks || {})) { + const hookFnName = pathNormalization(part, regHookFnName, hookName); + try { + const hookFn = await loadServerHook(hookFnName, hookName); + if (!hookFn) throw new Error('Not a function'); + if (hooksByName[hookName] == null) hooksByName[hookName] = []; + hooksByName[hookName].push({ + hook_name: hookName, + hook_fn: hookFn, + hook_fn_name: hookFnName, + part, + }); + } catch (err) { + console.error(`Failed to load hook function "${hookFnName}" for plugin "${part.plugin}" ` + + `part "${part.name}" hook set "hooks" hook "${hookName}": ` + + `${err.stack || err}`); + } + } + } + return hooksByName; +}; + export const update = async () => { const packages = await getPackages(); const parts = {}; // Key is full name. sortParts converts this into a topologically sorted array. @@ -121,7 +192,7 @@ export const update = async () => { defs.plugins = plugins; defs.parts = sortParts(parts); - defs.hooks = pluginUtils.extractHooks(defs.parts, 'hooks', pathNormalization); + defs.hooks = await extractServerHooks(defs.parts); defs.loaded = true; await Promise.all(Object.keys(defs.plugins).map(async (p) => { const logger = log4js.getLogger(`plugin:${p}`); @@ -130,9 +201,8 @@ export const update = async () => { }; export const getPackages = async () => { - // Lazily resolved via `createRequire` to avoid a circular ESM import between - // `plugins.ts` and `installer.ts`. - const {linkInstaller} = requireFromHere('./installer'); + // Lazily import to avoid a circular dependency between `plugins.ts` and `installer.ts`. + const {linkInstaller} = await import('./installer.js'); const plugins = await linkInstaller.listPlugins(); const newDependencies = {}; diff --git a/src/static/js/pluginfw/shared.ts b/src/static/js/pluginfw/shared.ts index ec66675bf..4dd066231 100644 --- a/src/static/js/pluginfw/shared.ts +++ b/src/static/js/pluginfw/shared.ts @@ -1,14 +1,8 @@ // @ts-nocheck 'use strict'; -import {createRequire} from 'node:module'; import defs from './plugin_defs.js'; -// `createRequire` gives us a synchronous CommonJS-style `require` even though this file is now -// ESM. This is needed to keep the existing plugin contract (CJS plugins via `module.exports`) -// working when `loadFn` loads a plugin entry path at runtime. See `doc/plugins.md`. -const requireFromHere = createRequire(import.meta.url); - const disabledHookReasons = { hooks: { indexCustomInlineScripts: 'The hook makes it impossible to use a Content Security Policy ' + @@ -16,6 +10,29 @@ const disabledHookReasons = { }, }; +const loadModule = (path, modules) => { + if (modules !== undefined && 'get' in modules) return modules.get(path); + if (typeof require !== 'function') throw new Error('dynamic hook loading unavailable'); + return require(path); +}; + +const getHookFunction = (fn, functionName) => { + const namespaces = [fn, fn?.default].filter((ns) => ns != null); + for (const namespace of namespaces) { + let hookFn = namespace; + let missing = false; + for (const name of functionName.split('.')) { + if (hookFn == null || !(name in hookFn)) { + missing = true; + break; + } + hookFn = hookFn[name]; + } + if (!missing) return hookFn; + } + return undefined; +}; + const loadFn = (path, hookName, modules) => { let functionName; const parts = path.split(':'); @@ -31,18 +48,8 @@ const loadFn = (path, hookName, modules) => { functionName = parts[1]; } - let fn - if (modules === undefined || !("get" in modules)) { - fn = requireFromHere(/* webpackIgnore: true */ path); - } else { - fn = modules.get(path); - } - functionName = functionName ? functionName : hookName; - - for (const name of functionName.split('.')) { - fn = fn[name]; - } + let fn = getHookFunction(loadModule(path, modules), functionName); return fn; }; diff --git a/src/static/js/rjquery.ts b/src/static/js/rjquery.ts index 9a28da15f..163201d90 100644 --- a/src/static/js/rjquery.ts +++ b/src/static/js/rjquery.ts @@ -6,3 +6,4 @@ window.$ = $; const jq = window.$.noConflict(true); export {jq as jQuery, jq as $}; +export default jq; diff --git a/src/static/js/skin_variants.ts b/src/static/js/skin_variants.ts index 71d6618b7..ca6deb9cc 100644 --- a/src/static/js/skin_variants.ts +++ b/src/static/js/skin_variants.ts @@ -79,3 +79,10 @@ if (window.location.hash.toLowerCase() === '#skinvariantsbuilder') { } export {isDarkMode, setDarkModeInLocalStorage, isWhiteModeEnabledInLocalStorage, isDarkModeEnabledInLocalStorage, updateSkinVariantsClasses}; +export default { + isDarkMode, + setDarkModeInLocalStorage, + isWhiteModeEnabledInLocalStorage, + isDarkModeEnabledInLocalStorage, + updateSkinVariantsClasses, +}; diff --git a/src/static/js/socketio.ts b/src/static/js/socketio.ts index 52ee4b1bc..aab64ff7d 100644 --- a/src/static/js/socketio.ts +++ b/src/static/js/socketio.ts @@ -42,8 +42,11 @@ const connect = (etherpadBaseUrl, namespace = '/', options = {}) => { return socket; }; -if (typeof exports === 'object') { - exports.connect = connect; -} else { - window.socketio = {connect}; +const socketio = {connect}; + +if (typeof window !== 'undefined') { + window.socketio = socketio; } + +export {connect}; +export default socketio; diff --git a/src/static/js/vendors/browser.ts b/src/static/js/vendors/browser.ts index a785d8a8e..4d8eba229 100644 --- a/src/static/js/vendors/browser.ts +++ b/src/static/js/vendors/browser.ts @@ -9,18 +9,13 @@ * MIT License | (c) Dustin Diaz 2015 */ -!function (name, definition) { - if (typeof module != 'undefined' && module.exports) module.exports = definition() - else if (typeof define == 'function' && define.amd) define(definition) - else this[name] = definition() -}('bowser', function () { - /** - * See useragents.js for examples of navigator.userAgent - */ +/** + * See useragents.js for examples of navigator.userAgent + */ - var t = true +const t = true; - function detect(ua) { +function detect(ua) { function getFirstMatch(regex) { var match = ua.match(regex); @@ -284,28 +279,28 @@ } else result.x = t return result - } +} - var bowser = detect(typeof navigator !== 'undefined' ? navigator.userAgent : '') +const bowser = detect(typeof navigator !== 'undefined' ? navigator.userAgent : ''); - bowser.test = function (browserList) { - for (var i = 0; i < browserList.length; ++i) { - var browserItem = browserList[i]; - if (typeof browserItem=== 'string') { - if (browserItem in bowser) { - return true; - } +bowser.test = function (browserList) { + for (let i = 0; i < browserList.length; ++i) { + const browserItem = browserList[i]; + if (typeof browserItem=== 'string') { + if (browserItem in bowser) { + return true; } } - return false; } + return false; +}; - /* - * Set our detect method to the main bowser object so we can - * reuse it to test other user agents. - * This is needed to implement future tests. - */ - bowser._detect = detect; +/* + * Set our detect method to the main bowser object so we can + * reuse it to test other user agents. + * This is needed to implement future tests. + */ +bowser._detect = detect; - return bowser -}); +export {detect}; +export default bowser; diff --git a/src/tests/backend/common.ts b/src/tests/backend/common.ts index ea1f50911..86fee4236 100644 --- a/src/tests/backend/common.ts +++ b/src/tests/backend/common.ts @@ -1,6 +1,7 @@ 'use strict'; import {MapArrayType} from "../../node/types/MapType.js"; +import {afterAll, beforeAll} from 'vitest'; import AttributePool from '../../static/js/AttributePool.js'; import {strict as assert} from 'assert'; @@ -32,10 +33,9 @@ const logLevel = logger.level; // https://github.com/mochajs/mocha/issues/2640 process.on('unhandledRejection', (reason: string) => { throw reason; }); -before(async function () { - this.timeout(60000); +beforeAll(async () => { await init(); -}); +}, 60000); export const generateJWTToken = () => { @@ -82,6 +82,7 @@ export const init = async function () { settings.importExportRateLimiting = {max: 999999}; settings.commitRateLimiting = {duration: 0.001, points: 1e6}; httpServer = await server.start(); + if (httpServer == null) throw new Error('server.start() did not return an HTTP server'); // @ts-ignore baseUrl = `http://localhost:${httpServer!.address()!.port}`; logger.debug(`HTTP server at ${baseUrl}`); @@ -92,7 +93,7 @@ export const init = async function () { backups.authnFailureDelayMs = webaccess.authnFailureDelayMs; webaccess.setAuthnFailureDelayMs(0); - after(async function () { + afterAll(async () => { webaccess.setAuthnFailureDelayMs(backups.authnFailureDelayMs); // Note: This does not unset settings that were added. Object.assign(settings, backups.settings); diff --git a/src/tests/backend/specs/api/api.ts b/src/tests/backend/specs/api/api.ts index c024b4e66..6409f6aed 100644 --- a/src/tests/backend/specs/api/api.ts +++ b/src/tests/backend/specs/api/api.ts @@ -47,7 +47,6 @@ describe(__filename, function () { }); it('can obtain valid openapi definition document', async function () { - this.timeout(15000); await agent.get('/api/openapi.json') .expect(200) .expect((res:any) => { @@ -106,7 +105,6 @@ describe(__filename, function () { }); it('/api/openapi.json exposes apiKey security in apikey mode', async function () { - this.timeout(15000); const res = await agent.get('/api/openapi.json').expect(200); const schemes = res.body.components.securitySchemes; const hasApiKey = Object.values(schemes).some((s: any) => s.type === 'apiKey'); diff --git a/src/tests/backend/specs/api/importexport.ts b/src/tests/backend/specs/api/importexport.ts index 516d4159f..86d57099a 100644 --- a/src/tests/backend/specs/api/importexport.ts +++ b/src/tests/backend/specs/api/importexport.ts @@ -230,7 +230,6 @@ const testImports:MapArrayType = { }; describe(__filename, function () { - this.timeout(1000); before(async function () { agent = await common.init(); }); diff --git a/src/tests/backend/specs/api/importexportGetPost.ts b/src/tests/backend/specs/api/importexportGetPost.ts index 4d6167a94..8e76d6d01 100644 --- a/src/tests/backend/specs/api/importexportGetPost.ts +++ b/src/tests/backend/specs/api/importexportGetPost.ts @@ -41,7 +41,6 @@ const deleteTestPad = async () => { }; describe(__filename, function () { - this.timeout(45000); before(async function () { agent = await common.init(); }); describe('Connectivity', function () { @@ -319,7 +318,6 @@ describe(__filename, function () { }); // End of LibreOffice tests. it('Tries to import .etherpad', async function () { - this.timeout(3000); await agent.post(`/p/${testPadId}/import`) .set("authorization", await common.generateJWTToken()) .attach('file', etherpadDoc, { @@ -336,7 +334,6 @@ describe(__filename, function () { }); it('exports Etherpad', async function () { - this.timeout(3000); await agent.get(`/p/${testPadId}/export/etherpad`) .set("authorization", await common.generateJWTToken()) .buffer(true).parse(superagent.parse.text) @@ -345,7 +342,6 @@ describe(__filename, function () { }); it('exports HTML for this Etherpad file', async function () { - this.timeout(3000); await agent.get(`/p/${testPadId}/export/html`) .set("authorization", await common.generateJWTToken()) .expect(200) @@ -354,7 +350,6 @@ describe(__filename, function () { }); it('Tries to import unsupported file type', async function () { - this.timeout(3000); settings.allowUnknownFileEnds = false; await agent.post(`/p/${testPadId}/import`) .set("authorization", await common.generateJWTToken()) @@ -685,7 +680,6 @@ describe(__filename, function () { return pad; }; - this.timeout(1000); beforeEach(async function () { await deleteTestPad(); diff --git a/src/tests/backend/specs/apicalls.ts b/src/tests/backend/specs/apicalls.ts index 210d68071..903c3aacb 100644 --- a/src/tests/backend/specs/apicalls.ts +++ b/src/tests/backend/specs/apicalls.ts @@ -8,7 +8,6 @@ const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); describe(__filename, function () { - this.timeout(30000); let agent: any; before(async function () { agent = await common.init(); }); diff --git a/src/tests/backend/specs/clientvar_rev_consistency.ts b/src/tests/backend/specs/clientvar_rev_consistency.ts index 2f965c9a6..67b80c06c 100644 --- a/src/tests/backend/specs/clientvar_rev_consistency.ts +++ b/src/tests/backend/specs/clientvar_rev_consistency.ts @@ -49,7 +49,6 @@ describe(__filename, function () { }); it('CLIENT_VARS rev matches initialAttributedText state at that exact rev', async function () { - this.timeout(30000); const padId = randomString(10); // Create a pad with initial text @@ -97,7 +96,6 @@ describe(__filename, function () { // (b) lands several edits during that delay. // The bug also applied at higher load — to also reproduce the load // scenario, we pre-populate the pad with many revisions before connecting. - this.timeout(60000); const padId = randomString(10); const pad = await padManager.getPad(padId, 'rev0\n'); @@ -173,7 +171,6 @@ describe(__filename, function () { }); it('client receives revisions created during clientVars hook await window', async function () { - this.timeout(30000); const padId = randomString(10); const pad = await padManager.getPad(padId, 'start\n'); diff --git a/src/tests/backend/specs/largePaste.ts b/src/tests/backend/specs/largePaste.ts index fa42933f8..ef57240a7 100644 --- a/src/tests/backend/specs/largePaste.ts +++ b/src/tests/backend/specs/largePaste.ts @@ -22,7 +22,6 @@ describe(__filename, function () { }); it('can set and retrieve 50,000 characters of text on a pad', async function () { - this.timeout(30000); const padId = `largePasteTest${Date.now()}`; const largeText = 'A'.repeat(50000); diff --git a/src/tests/backend/specs/socketio.ts b/src/tests/backend/specs/socketio.ts index d9b66e50e..20427892a 100644 --- a/src/tests/backend/specs/socketio.ts +++ b/src/tests/backend/specs/socketio.ts @@ -18,7 +18,6 @@ const __dirname = dirname(__filename); const plugins = pluginDefs; describe(__filename, function () { - this.timeout(30000); let agent: any; let authorize:Function; const backups:MapArrayType = {}; diff --git a/src/tests/backend/specs/specialpages.ts b/src/tests/backend/specs/specialpages.ts index c4802a39d..77731dfad 100644 --- a/src/tests/backend/specs/specialpages.ts +++ b/src/tests/backend/specs/specialpages.ts @@ -14,7 +14,6 @@ const __dirname = dirname(__filename); describe(__filename, function () { - this.timeout(30000); let agent:any; const backups:MapArrayType = {}; before(async function () { agent = await common.init(); }); diff --git a/src/tests/backend/specs/undo_clear_authorship.ts b/src/tests/backend/specs/undo_clear_authorship.ts index f3568ff6d..a5c643ca4 100644 --- a/src/tests/backend/specs/undo_clear_authorship.ts +++ b/src/tests/backend/specs/undo_clear_authorship.ts @@ -133,7 +133,6 @@ describe(__filename, function () { describe('undo of clear authorship colors (bug #2802)', function () { it('should not disconnect when undoing clear authorship with multiple authors', async function () { - this.timeout(30000); // Step 1: Connect User A const userA = await connectUser(); diff --git a/src/tests/backend/specs/webaccess.ts b/src/tests/backend/specs/webaccess.ts index e576a24ab..5814ad5ee 100644 --- a/src/tests/backend/specs/webaccess.ts +++ b/src/tests/backend/specs/webaccess.ts @@ -17,7 +17,6 @@ const __dirname = dirname(__filename); const plugins = pluginDefs; describe(__filename, function () { - this.timeout(30000); let agent:any; const backups:MapArrayType = {}; const authHookNames = ['preAuthorize', 'authenticate', 'authorize']; diff --git a/src/tests/backend/vitest.setup.ts b/src/tests/backend/vitest.setup.ts new file mode 100644 index 000000000..7bc6b2b53 --- /dev/null +++ b/src/tests/backend/vitest.setup.ts @@ -0,0 +1,13 @@ +import {afterAll, beforeAll, describe, it} from 'vitest'; + +process.env.NODE_ENV = 'production'; +process.env.AUTHENTICATION_METHOD = 'sso'; + +Object.assign(globalThis, { + after: afterAll, + before: beforeAll, + context: describe, + specify: it, + xdescribe: describe.skip, + xit: it.skip, +}); diff --git a/src/tsconfig.json b/src/tsconfig.json index c946e1280..0e7a95663 100644 --- a/src/tsconfig.json +++ b/src/tsconfig.json @@ -15,7 +15,7 @@ /* Completeness */ "skipLibCheck": true /* Skip type checking all .d.ts files. */, "resolveJsonModule": true, - "types": ["node", "jquery", "mocha"] + "types": ["node", "jquery", "mocha", "vitest/globals"] }, "exclude": ["../plugin_packages", "node_modules"] } diff --git a/src/vitest.config.ts b/src/vitest.config.ts index c47c424cc..0f38d98a7 100644 --- a/src/vitest.config.ts +++ b/src/vitest.config.ts @@ -1,7 +1,18 @@ -import { defineConfig } from 'vitest/config' +import {defineConfig} from 'vitest/config'; export default defineConfig({ test: { - include: ["tests/backend-new/specs/**/*.ts"], + globals: true, + setupFiles: ['./tests/backend/vitest.setup.ts'], + include: [ + 'tests/backend-new/specs/**/*.ts', + 'tests/backend/specs/**/*.ts', + 'tests/container/specs/**/*.ts', + ], + exclude: [ + 'tests/backend/specs/api/fuzzImportTest.ts', + ], + hookTimeout: 60000, + testTimeout: 120000, }, -}) +});