From 0d3025b8cfd29c69255bde2d725aa786bd722d09 Mon Sep 17 00:00:00 2001 From: Andrew Rumble Date: Mon, 28 Apr 2025 11:25:12 +0100 Subject: [PATCH] Add vitest and configuration GitOrigin-RevId: 1262f9f32a0db6a29d3feedd8158b8dd04e48b6a --- package-lock.json | 737 ++++++++++++++++++++ services/web/.eslintrc.js | 24 + services/web/Makefile | 10 + services/web/bin/test_unit_run_dir | 45 ++ services/web/docker-compose.ci.yml | 1 + services/web/package.json | 6 +- services/web/test/unit/bootstrap.js | 25 +- services/web/test/unit/common_bootstrap.js | 24 + services/web/test/unit/vitest_bootstrap.mjs | 29 + services/web/vitest.config.js | 12 + 10 files changed, 888 insertions(+), 25 deletions(-) create mode 100755 services/web/bin/test_unit_run_dir create mode 100644 services/web/test/unit/common_bootstrap.js create mode 100644 services/web/test/unit/vitest_bootstrap.mjs create mode 100644 services/web/vitest.config.js diff --git a/package-lock.json b/package-lock.json index 4a14efb544..146ba3255d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -44850,6 +44850,7 @@ "@uppy/react": "^3.2.1", "@uppy/utils": "^5.7.0", "@uppy/xhr-upload": "^3.6.0", + "@vitest/eslint-plugin": "1.1.44", "5to6-codemod": "^1.8.0", "abort-controller": "^3.0.0", "acorn": "^7.1.1", @@ -44956,6 +44957,7 @@ "tty-browserify": "^0.0.1", "typescript": "^5.0.4", "uuid": "^9.0.1", + "vitest": "^3.1.2", "w3c-keyname": "^2.2.8", "webpack": "^5.98.0", "webpack-assets-manifest": "^5.2.1", @@ -44965,6 +44967,26 @@ "yup": "^0.32.11" } }, + "services/web/node_modules/@eslint-community/eslint-utils": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz", + "integrity": "sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, "services/web/node_modules/@google-cloud/bigquery": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/@google-cloud/bigquery/-/bigquery-6.0.3.tgz", @@ -45161,6 +45183,18 @@ "dev": true, "license": "MIT" }, + "services/web/node_modules/@types/debug": { + "version": "4.1.12", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", + "integrity": "sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@types/ms": "*" + } + }, "services/web/node_modules/@types/express": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", @@ -45179,6 +45213,143 @@ "integrity": "sha512-Z61JK7DKDtdKTWwLeElSEBcWGRLY8g95ic5FoQqI9CMx0ns/Ghep3B4DfcEimiKMvtamNVULVNKEsiwV3aQmXw==", "dev": true }, + "services/web/node_modules/@typescript-eslint/scope-manager": { + "version": "8.32.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.32.1.tgz", + "integrity": "sha512-7IsIaIDeZn7kffk7qXC3o6Z4UblZJKV3UBpkvRNpr5NSyLji7tvTcvmnMNYuYLyh26mN8W723xpo3i4MlD33vA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@typescript-eslint/types": "8.32.1", + "@typescript-eslint/visitor-keys": "8.32.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "services/web/node_modules/@typescript-eslint/types": { + "version": "8.32.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.32.1.tgz", + "integrity": "sha512-YmybwXUJcgGqgAp6bEsgpPXEg6dcCyPyCSr0CAAueacR/CCBi25G3V8gGQ2kRzQRBNol7VQknxMs9HvVa9Rvfg==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "services/web/node_modules/@typescript-eslint/typescript-estree": { + "version": "8.32.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.32.1.tgz", + "integrity": "sha512-Y3AP9EIfYwBb4kWGb+simvPaqQoT5oJuzzj9m0i6FCY6SPvlomY2Ei4UEMm7+FXtlNJbor80ximyslzaQF6xhg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@typescript-eslint/types": "8.32.1", + "@typescript-eslint/visitor-keys": "8.32.1", + "debug": "^4.3.4", + "fast-glob": "^3.3.2", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <5.9.0" + } + }, + "services/web/node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "peer": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "services/web/node_modules/@typescript-eslint/utils": { + "version": "8.32.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.32.1.tgz", + "integrity": "sha512-DsSFNIgLSrc89gpq1LJB7Hm1YpuhK086DRDJSNrewcGvYloWW1vZLHBTIvarKZDcAORIy/uWNx8Gad+4oMpkSA==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.7.0", + "@typescript-eslint/scope-manager": "8.32.1", + "@typescript-eslint/types": "8.32.1", + "@typescript-eslint/typescript-estree": "8.32.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" + } + }, + "services/web/node_modules/@typescript-eslint/visitor-keys": { + "version": "8.32.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.32.1.tgz", + "integrity": "sha512-ar0tjQfObzhSaW3C3QNmTc5ofj0hDoNQ5XWrCy6zDyabdr0TWhCkClp+rywGNj/odAFBVzzJrK4tEq5M4Hmu4w==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@typescript-eslint/types": "8.32.1", + "eslint-visitor-keys": "^4.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "services/web/node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, "services/web/node_modules/@uppy/core": { "version": "3.8.0", "resolved": "https://registry.npmjs.org/@uppy/core/-/core-3.8.0.tgz", @@ -45332,6 +45503,130 @@ "@uppy/core": "^3.8.0" } }, + "services/web/node_modules/@vitest/eslint-plugin": { + "version": "1.1.44", + "resolved": "https://registry.npmjs.org/@vitest/eslint-plugin/-/eslint-plugin-1.1.44.tgz", + "integrity": "sha512-m4XeohMT+Dj2RZfxnbiFR+Cv5dEC0H7C6TlxRQT7GK2556solm99kxgzJp/trKrZvanZcOFyw7aABykUTfWyrg==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@typescript-eslint/utils": ">= 8.24.0", + "eslint": ">= 8.57.0", + "typescript": ">= 5.0.0", + "vitest": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + }, + "vitest": { + "optional": true + } + } + }, + "services/web/node_modules/@vitest/expect": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.1.4.tgz", + "integrity": "sha512-xkD/ljeliyaClDYqHPNCiJ0plY5YIcM0OlRiZizLhlPmpXWpxnGMyTZXOHFhFeG7w9P5PBeL4IdtJ/HeQwTbQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "3.1.4", + "@vitest/utils": "3.1.4", + "chai": "^5.2.0", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "services/web/node_modules/@vitest/expect/node_modules/chai": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.2.0.tgz", + "integrity": "sha512-mCuXncKXk5iCLhfhwTc0izo0gtEmpz5CtG2y8GiOINBlMVS6v8TMRc5TaLWKS6692m9+dVVfzgeVxR5UxWHTYw==", + "dev": true, + "license": "MIT", + "dependencies": { + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "services/web/node_modules/@vitest/pretty-format": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.1.4.tgz", + "integrity": "sha512-cqv9H9GvAEoTaoq+cYqUTCGscUjKqlJZC7PRwY5FMySVj5J+xOm1KQcCiYHJOEzOKRUhLH4R2pTwvFlWCEScsg==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "services/web/node_modules/@vitest/runner": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.1.4.tgz", + "integrity": "sha512-djTeF1/vt985I/wpKVFBMWUlk/I7mb5hmD5oP8K9ACRmVXgKTae3TUOtXAEBfslNKPzUQvnKhNd34nnRSYgLNQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "3.1.4", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "services/web/node_modules/@vitest/snapshot": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.1.4.tgz", + "integrity": "sha512-JPHf68DvuO7vilmvwdPr9TS0SuuIzHvxeaCkxYcCD4jTk67XwL45ZhEHFKIuCm8CYstgI6LZ4XbwD6ANrwMpFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.1.4", + "magic-string": "^0.30.17", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "services/web/node_modules/@vitest/spy": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.1.4.tgz", + "integrity": "sha512-Xg1bXhu+vtPXIodYN369M86K8shGLouNjoVI78g8iAq2rFoHFdajNvJJ5A/9bPMFcfQqdaCpOgWKEoMQg/s0Yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyspy": "^3.0.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "services/web/node_modules/@vitest/utils": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.1.4.tgz", + "integrity": "sha512-yriMuO1cfFhmiGc8ataN51+9ooHRuURdfAZfwFd3usWynjzpLslZdYnRegTv32qdgtJTsj15FoeZe2g15fY1gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.1.4", + "loupe": "^3.1.3", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, "services/web/node_modules/agent-base": { "version": "7.1.3", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.3.tgz", @@ -45368,6 +45663,16 @@ "ajv": "^8.8.2" } }, + "services/web/node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, "services/web/node_modules/base-x": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/base-x/-/base-x-4.0.1.tgz", @@ -45405,6 +45710,16 @@ "ieee754": "^1.2.1" } }, + "services/web/node_modules/check-error": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz", + "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + } + }, "services/web/node_modules/csv": { "version": "6.2.5", "resolved": "https://registry.npmjs.org/csv/-/csv-6.2.5.tgz", @@ -45451,6 +45766,16 @@ } } }, + "services/web/node_modules/deep-eql": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "services/web/node_modules/duplexify": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.2.tgz", @@ -45462,6 +45787,20 @@ "stream-shift": "^1.0.0" } }, + "services/web/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, "services/web/node_modules/esmock": { "version": "2.6.7", "resolved": "https://registry.npmjs.org/esmock/-/esmock-2.6.7.tgz", @@ -45479,6 +45818,21 @@ "node": ">=0.8.x" } }, + "services/web/node_modules/fdir": { + "version": "6.4.4", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.4.tgz", + "integrity": "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, "services/web/node_modules/fetch-mock": { "version": "12.5.2", "resolved": "https://registry.npmjs.org/fetch-mock/-/fetch-mock-12.5.2.tgz", @@ -45598,6 +45952,18 @@ "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==", "dev": true }, + "services/web/node_modules/jiti": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.4.2.tgz", + "integrity": "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, "services/web/node_modules/json-schema-traverse": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", @@ -45609,6 +45975,13 @@ "integrity": "sha512-gKO5uExCXvSm6zbF562EvM+rd1kQDnB9AZBbiQVzf1ZmdDpxUSvpnAaVOP83N/31mRK8Ml8/VE8DMvsAZQ+7wg==", "dev": true }, + "services/web/node_modules/loupe": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.3.tgz", + "integrity": "sha512-kkIp7XSkP78ZxJEsSxW3712C6teJVoeHHwgo9zJ380de7IYyJ2ISlxojcH2pC5OFLewESmnRi/+XCDIEEVyoug==", + "dev": true, + "license": "MIT" + }, "services/web/node_modules/lru-cache": { "version": "7.10.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.10.1.tgz", @@ -45735,6 +46108,29 @@ "isarray": "0.0.1" } }, + "services/web/node_modules/pathval": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.0.tgz", + "integrity": "sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.16" + } + }, + "services/web/node_modules/picomatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "services/web/node_modules/retry-request": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/retry-request/-/retry-request-5.0.2.tgz", @@ -45778,6 +46174,20 @@ "url": "https://opencollective.com/webpack" } }, + "services/web/node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "dev": true, + "license": "ISC", + "peer": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "services/web/node_modules/sinon": { "version": "7.5.0", "resolved": "https://registry.npmjs.org/sinon/-/sinon-7.5.0.tgz", @@ -45941,6 +46351,30 @@ } } }, + "services/web/node_modules/tinyrainbow": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz", + "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "services/web/node_modules/ts-api-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", + "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, "services/web/node_modules/uuid": { "version": "9.0.1", "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", @@ -45953,6 +46387,294 @@ "uuid": "dist/bin/uuid" } }, + "services/web/node_modules/vite-node": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.1.4.tgz", + "integrity": "sha512-6enNwYnpyDo4hEgytbmc6mYWHXDHYEn0D1/rw4Q+tnHUGtKTJsn8T1YkX6Q18wI5LCrS8CTYlBaiCqxOy2kvUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.4.0", + "es-module-lexer": "^1.7.0", + "pathe": "^2.0.3", + "vite": "^5.0.0 || ^6.0.0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "services/web/node_modules/vite-node/node_modules/vite": { + "version": "6.3.5", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz", + "integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "services/web/node_modules/vitest": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.1.4.tgz", + "integrity": "sha512-Ta56rT7uWxCSJXlBtKgIlApJnT6e6IGmTYxYcmxjJ4ujuZDI59GUQgVDObXXJujOmPDBYXHK1qmaGtneu6TNIQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "3.1.4", + "@vitest/mocker": "3.1.4", + "@vitest/pretty-format": "^3.1.4", + "@vitest/runner": "3.1.4", + "@vitest/snapshot": "3.1.4", + "@vitest/spy": "3.1.4", + "@vitest/utils": "3.1.4", + "chai": "^5.2.0", + "debug": "^4.4.0", + "expect-type": "^1.2.1", + "magic-string": "^0.30.17", + "pathe": "^2.0.3", + "std-env": "^3.9.0", + "tinybench": "^2.9.0", + "tinyexec": "^0.3.2", + "tinyglobby": "^0.2.13", + "tinypool": "^1.0.2", + "tinyrainbow": "^2.0.0", + "vite": "^5.0.0 || ^6.0.0", + "vite-node": "3.1.4", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/debug": "^4.1.12", + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "@vitest/browser": "3.1.4", + "@vitest/ui": "3.1.4", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/debug": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "services/web/node_modules/vitest/node_modules/@vitest/mocker": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.1.4.tgz", + "integrity": "sha512-8IJ3CvwtSw/EFXqWFL8aCMu+YyYXG2WUSrQbViOZkWTKTVicVwZ/YiEZDSqD00kX+v/+W+OnxhNWoeVKorHygA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "3.1.4", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.17" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^5.0.0 || ^6.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "services/web/node_modules/vitest/node_modules/chai": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.2.0.tgz", + "integrity": "sha512-mCuXncKXk5iCLhfhwTc0izo0gtEmpz5CtG2y8GiOINBlMVS6v8TMRc5TaLWKS6692m9+dVVfzgeVxR5UxWHTYw==", + "dev": true, + "license": "MIT", + "dependencies": { + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "services/web/node_modules/vitest/node_modules/vite": { + "version": "6.3.5", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz", + "integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, "services/web/node_modules/xml-crypto": { "version": "2.1.6", "resolved": "https://registry.npmjs.org/xml-crypto/-/xml-crypto-2.1.6.tgz", @@ -45980,6 +46702,21 @@ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, + "services/web/node_modules/yaml": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.0.tgz", + "integrity": "sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==", + "dev": true, + "license": "ISC", + "optional": true, + "peer": true, + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + } + }, "tools/saas-e2e": { "name": "@overleaf/saas-e2e", "devDependencies": { diff --git a/services/web/.eslintrc.js b/services/web/.eslintrc.js index 3c672de7e7..b505080b98 100644 --- a/services/web/.eslintrc.js +++ b/services/web/.eslintrc.js @@ -64,6 +64,10 @@ module.exports = { { // Test specific rules files: ['**/test/**/*.*'], + excludedFiles: [ + '**/test/unit/src/**/*.test.mjs', + 'test/unit/vitest_bootstrap.mjs', + ], // exclude vitest files plugins: ['mocha', 'chai-expect', 'chai-friendly'], env: { mocha: true, @@ -95,6 +99,26 @@ module.exports = { '@typescript-eslint/no-unused-expressions': 'off', }, }, + { + files: [ + '**/test/unit/src/**/*.test.mjs', + 'test/unit/vitest_bootstrap.mjs', + ], + env: { + jest: true, // best match for vitest API etc. + }, + plugins: ['@vitest', 'chai-expect', 'chai-friendly'], // still using chai for now + rules: { + // Swap the no-unused-expressions rule with a more chai-friendly one + 'no-unused-expressions': 'off', + 'chai-friendly/no-unused-expressions': 'error', + + // chai-specific rules + 'chai-expect/missing-assertion': 'error', + 'chai-expect/terminating-properties': 'error', + '@typescript-eslint/no-unused-expressions': 'off', + }, + }, { // ES specific rules files: [ diff --git a/services/web/Makefile b/services/web/Makefile index c6916048d6..58323058b8 100644 --- a/services/web/Makefile +++ b/services/web/Makefile @@ -83,6 +83,16 @@ test_unit_app: $(DOCKER_COMPOSE) run --name unit_test_$(BUILD_DIR_NAME) --rm test_unit $(DOCKER_COMPOSE) down -v -t 0 +test_unit_esm: export COMPOSE_PROJECT_NAME=unit_test_esm_$(BUILD_DIR_NAME) +test_unit_esm: + $(DOCKER_COMPOSE) run --rm test_unit npm run test:unit:esm + $(DOCKER_COMPOSE) down -v -t 0 + +test_unit_esm_watch: export COMPOSE_PROJECT_NAME=unit_test_esm_watch_$(BUILD_DIR_NAME) +test_unit_esm_watch: + $(DOCKER_COMPOSE) run --rm test_unit npm run test:unit:esm:watch + $(DOCKER_COMPOSE) down -v -t 0 + TEST_SUITES = $(sort $(filter-out \ $(wildcard test/unit/src/helpers/*), \ $(wildcard test/unit/src/*/*))) diff --git a/services/web/bin/test_unit_run_dir b/services/web/bin/test_unit_run_dir new file mode 100755 index 0000000000..20f580cf06 --- /dev/null +++ b/services/web/bin/test_unit_run_dir @@ -0,0 +1,45 @@ +#!/bin/bash + +TARGET_DIR=$1 + + +declare -a vitest_args=("$TARGET_DIR") + +if [[ -n "$MOCHA_GREP" ]]; then + vitest_args+=("--testNamePattern" "$MOCHA_GREP") +fi + +if [[ -n "$VITEST_NO_CACHE" ]]; then + echo "Disabling cache for vitest." + vitest_args+=("--no-cache") +fi + +echo "Running unit tests in directory: $TARGET_DIR" + +npm run test:unit:esm -- "${vitest_args[@]}" + +vitest_status=$? + +if find "$TARGET_DIR" -type f -name "*.js" -print -quit | grep -q '.'; then + mocha --recursive --timeout 25000 --exit --grep="$MOCHA_GREP" --require test/unit/bootstrap.js --extension=js "$TARGET_DIR" + mocha_status=$? +else + echo "No mocha tests found in $TARGET_DIR, skipping mocha step." + mocha_status=0 +fi + +if [ $mocha_status -eq 0 ] && [ $vitest_status -eq 0 ]; then + exit 0 +fi + +# Report status briefly at the end for failures + +if [ $mocha_status -ne 0 ]; then + echo "Mocha tests failed with status: $mocha_status" +fi + +if [ $vitest_status -ne 0 ]; then + echo "Vitest tests failed with status: $vitest_status" +fi + +exit 1 diff --git a/services/web/docker-compose.ci.yml b/services/web/docker-compose.ci.yml index 164cc22c5a..5cffe19810 100644 --- a/services/web/docker-compose.ci.yml +++ b/services/web/docker-compose.ci.yml @@ -21,6 +21,7 @@ services: OVERLEAF_CONFIG: NODE_ENV: test NODE_OPTIONS: "--unhandled-rejections=strict" + VITEST_NO_CACHE: true depends_on: - mongo diff --git a/services/web/package.json b/services/web/package.json index 5080813d55..ee5f81d4f8 100644 --- a/services/web/package.json +++ b/services/web/package.json @@ -9,10 +9,12 @@ "scripts": { "test:acceptance:run_dir": "mocha --recursive --timeout 25000 --grep=$MOCHA_GREP --require test/acceptance/bootstrap.js", "test:acceptance:app": "npm run test:acceptance:run_dir -- test/acceptance/src", - "test:unit:run_dir": "mocha --recursive --timeout 25000 --exit --grep=$MOCHA_GREP --require test/unit/bootstrap.js", + "test:unit:run_dir": "bin/test_unit_run_dir", "test:unit:all": "npm run test:unit:run_dir -- test/unit/src modules/*/test/unit/src", "test:unit:all:silent": "npm run test:unit:all -- --reporter dot", "test:unit:app": "npm run test:unit:run_dir -- test/unit/src", + "test:unit:esm": "vitest run", + "test:unit:esm:watch": "vitest", "test:frontend": "NODE_ENV=test TZ=GMT mocha --recursive --timeout 5000 --exit --extension js,jsx,mjs,ts,tsx --grep=$MOCHA_GREP --require test/frontend/bootstrap.js --ignore '**/*.spec.{js,jsx,ts,tsx}' --ignore '**/helpers/**/*.{js,jsx,ts,tsx}' test/frontend modules/*/test/frontend", "test:frontend:coverage": "c8 --all --include 'frontend/js' --include 'modules/*/frontend/js' --exclude 'frontend/js/vendor' --reporter=lcov --reporter=text-summary npm run test:frontend", "start": "node app.mjs", @@ -250,6 +252,7 @@ "@uppy/react": "^3.2.1", "@uppy/utils": "^5.7.0", "@uppy/xhr-upload": "^3.6.0", + "@vitest/eslint-plugin": "1.1.44", "5to6-codemod": "^1.8.0", "abort-controller": "^3.0.0", "acorn": "^7.1.1", @@ -356,6 +359,7 @@ "tty-browserify": "^0.0.1", "typescript": "^5.0.4", "uuid": "^9.0.1", + "vitest": "^3.1.2", "w3c-keyname": "^2.2.8", "webpack": "^5.98.0", "webpack-assets-manifest": "^5.2.1", diff --git a/services/web/test/unit/bootstrap.js b/services/web/test/unit/bootstrap.js index ee4a022c15..f3d3f382f2 100644 --- a/services/web/test/unit/bootstrap.js +++ b/services/web/test/unit/bootstrap.js @@ -1,29 +1,6 @@ const Path = require('path') -const chai = require('chai') const sinon = require('sinon') - -/* - * Chai configuration - */ - -// add chai.should() -chai.should() - -// Load sinon-chai assertions so expect(stubFn).to.have.been.calledWith('abc') -// has a nicer failure messages -chai.use(require('sinon-chai')) - -// Load promise support for chai -chai.use(require('chai-as-promised')) - -// Do not truncate assertion errors -chai.config.truncateThreshold = 0 - -// add support for mongoose in sinon -require('sinon-mongoose') - -// ensure every ObjectId has the id string as a property for correct comparisons -require('mongodb-legacy').ObjectId.cacheHexString = true +require('./common_bootstrap') /* * Global stubs diff --git a/services/web/test/unit/common_bootstrap.js b/services/web/test/unit/common_bootstrap.js new file mode 100644 index 0000000000..d74fee60b2 --- /dev/null +++ b/services/web/test/unit/common_bootstrap.js @@ -0,0 +1,24 @@ +const chai = require('chai') + +/* + * Chai configuration + */ + +// add chai.should() +chai.should() + +// Load sinon-chai assertions so expect(stubFn).to.have.been.calledWith('abc') +// has a nicer failure messages +chai.use(require('sinon-chai')) + +// Load promise support for chai +chai.use(require('chai-as-promised')) + +// Do not truncate assertion errors +chai.config.truncateThreshold = 0 + +// add support for mongoose in sinon +require('sinon-mongoose') + +// ensure every ObjectId has the id string as a property for correct comparisons +require('mongodb-legacy').ObjectId.cacheHexString = true diff --git a/services/web/test/unit/vitest_bootstrap.mjs b/services/web/test/unit/vitest_bootstrap.mjs new file mode 100644 index 0000000000..fc4d883b1a --- /dev/null +++ b/services/web/test/unit/vitest_bootstrap.mjs @@ -0,0 +1,29 @@ +import { vi } from 'vitest' +import './common_bootstrap.js' +import sinon from 'sinon' +import logger from '@overleaf/logger' + +vi.mock('@overleaf/logger', async () => { + const sinon = (await import('sinon')).default + return { + default: { + debug: sinon.stub(), + info: sinon.stub(), + log: sinon.stub(), + warn: sinon.stub(), + err: sinon.stub(), + error: sinon.stub(), + fatal: sinon.stub(), + }, + } +}) + +beforeEach(ctx => { + ctx.logger = logger +}) + +afterEach(() => { + vi.restoreAllMocks() + vi.resetModules() + sinon.restore() +}) diff --git a/services/web/vitest.config.js b/services/web/vitest.config.js new file mode 100644 index 0000000000..3b84690447 --- /dev/null +++ b/services/web/vitest.config.js @@ -0,0 +1,12 @@ +const { defineConfig } = require('vitest/config') + +module.exports = defineConfig({ + test: { + include: [ + 'modules/*/test/unit/**/*.test.mjs', + 'test/unit/src/**/*.test.mjs', + ], + setupFiles: ['./test/unit/vitest_bootstrap.mjs'], + globals: true, + }, +})