diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml new file mode 100644 index 00000000..dc931e81 --- /dev/null +++ b/.github/workflows/run-tests.yml @@ -0,0 +1,28 @@ +name: Run Tests + +on: + push: + branches: + - main + - 'release-*' + pull_request: + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: 20 + cache: npm + + - run: npm ci + + - run: npm run test:unit:coverage + + - name: Upload coverage to Coveralls + uses: coverallsapp/github-action@v2 + with: + path-to-lcov: coverage/lcov.info diff --git a/package-lock.json b/package-lock.json index 1523cd68..bbc0b683 100644 --- a/package-lock.json +++ b/package-lock.json @@ -77,6 +77,7 @@ "@types/uuid": "^9.0.8", "@vitejs/plugin-basic-ssl": "^1.0.2", "@vitejs/plugin-vue": "^6.0.1", + "@vitest/coverage-v8": "2.1.9", "@vue/eslint-config-prettier": "^10.2.0", "@vue/eslint-config-typescript": "^14.6.0", "@vue/tsconfig": "^0.8.1", @@ -88,6 +89,9 @@ "vite": "^5.4.21", "vitest": "^2.1.9", "vue-tsc": "^3.0.7" + }, + "optionalDependencies": { + "@rollup/rollup-linux-x64-gnu": "4.46.3" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -98,6 +102,20 @@ "node": ">=0.10.0" } }, + "node_modules/@ampproject/remapping": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz", + "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/@babel/helper-string-parser": { "version": "7.27.1", "license": "MIT", @@ -136,6 +154,13 @@ "node": ">=6.9.0" } }, + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", + "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", + "dev": true, + "license": "MIT" + }, "node_modules/@bufbuild/protobuf": { "version": "2.11.0", "devOptional": true, @@ -460,6 +485,34 @@ "url": "https://github.com/sponsors/nzakas" } }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.6.tgz", + "integrity": "sha512-+Sg6GCR/wy1oSmQDFq4LQDAhm3ETKnorxN+y5nbLULOR3P0c14f2Wurzj3/xqPXtasLFfHd5iRFQ7AJt4KH2cw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.13", "license": "MIT", @@ -628,6 +681,17 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, "node_modules/@pkgr/core": { "version": "0.2.9", "dev": true, @@ -722,6 +786,19 @@ "darwin" ] }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.46.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.46.3.tgz", + "integrity": "sha512-F42IgZI4JicE2vM2PWCe0N5mR5vR0gIdORPqhGQ32/u1S1v3kLtbZ0C/mi9FFk7C5T0PgdeyWEPajPjaUpyoKg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, "node_modules/@scarf/scarf": { "version": "1.4.0", "hasInstallScript": true, @@ -1499,6 +1576,39 @@ "vue": "^3.2.25" } }, + "node_modules/@vitest/coverage-v8": { + "version": "2.1.9", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-2.1.9.tgz", + "integrity": "sha512-Z2cOr0ksM00MpEfyVE8KXIYPEcBFxdbLSs56L8PO0QQMxt/6bDj45uQfxoc96v05KW3clk7vvgP0qfDit9DmfQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ampproject/remapping": "^2.3.0", + "@bcoe/v8-coverage": "^0.2.3", + "debug": "^4.3.7", + "istanbul-lib-coverage": "^3.2.2", + "istanbul-lib-report": "^3.0.1", + "istanbul-lib-source-maps": "^5.0.6", + "istanbul-reports": "^3.1.7", + "magic-string": "^0.30.12", + "magicast": "^0.3.5", + "std-env": "^3.8.0", + "test-exclude": "^7.0.1", + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@vitest/browser": "2.1.9", + "vitest": "2.1.9" + }, + "peerDependenciesMeta": { + "@vitest/browser": { + "optional": true + } + } + }, "node_modules/@vitest/expect": { "version": "2.1.9", "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.9.tgz", @@ -1973,6 +2083,19 @@ "node": ">=6" } }, + "node_modules/ansi-regex": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, "node_modules/ansi-styles": { "version": "4.3.0", "dev": true, @@ -3444,6 +3567,13 @@ "readable-stream": "^2.0.2" } }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true, + "license": "MIT" + }, "node_modules/ecdsa-sig-formatter": { "version": "1.0.11", "license": "Apache-2.0", @@ -3472,6 +3602,13 @@ "version": "4.12.2", "license": "MIT" }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, "node_modules/encodeurl": { "version": "2.0.0", "license": "MIT", @@ -4246,6 +4383,23 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/foreground-child": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz", + "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.6", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/form-data": { "version": "4.0.5", "license": "MIT", @@ -4367,6 +4521,28 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/glob": { + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "dev": true, + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/glob-parent": { "version": "6.0.2", "dev": true, @@ -4378,6 +4554,32 @@ "node": ">=10.13.0" } }, + "node_modules/glob/node_modules/brace-expansion": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.1.tgz", + "integrity": "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/glob/node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/globals": { "version": "13.24.0", "dev": true, @@ -4586,6 +4788,13 @@ "version": "5.5.3", "license": "MIT" }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true, + "license": "MIT" + }, "node_modules/html-url-attributes": { "version": "3.0.1", "license": "MIT", @@ -4903,6 +5112,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/is-generator-function": { "version": "1.1.0", "license": "MIT", @@ -5107,6 +5326,76 @@ "dev": true, "license": "ISC" }, + "node_modules/istanbul-lib-coverage": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=8" + } + }, + "node_modules/istanbul-lib-report": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^4.0.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-lib-source-maps": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz", + "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.23", + "debug": "^4.1.1", + "istanbul-lib-coverage": "^3.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/istanbul-reports": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, "node_modules/jiti": { "version": "2.6.1", "license": "MIT", @@ -5414,6 +5703,13 @@ "dev": true, "license": "MIT" }, + "node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, "node_modules/magic-string": { "version": "0.30.21", "license": "MIT", @@ -5421,6 +5717,34 @@ "@jridgewell/sourcemap-codec": "^1.5.5" } }, + "node_modules/magicast": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.3.5.tgz", + "integrity": "sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.25.4", + "@babel/types": "^7.25.4", + "source-map-js": "^1.2.0" + } + }, + "node_modules/make-dir": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/marked": { "version": "11.1.0", "license": "MIT", @@ -6093,6 +6417,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, "node_modules/moment": { "version": "2.29.4", "license": "MIT", @@ -6354,6 +6688,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/package-json-from-dist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "dev": true, + "license": "BlueOak-1.0.0" + }, "node_modules/pako": { "version": "1.0.11", "license": "(MIT AND Zlib)" @@ -6440,6 +6781,23 @@ "version": "1.0.7", "license": "MIT" }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/pathe": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", @@ -7799,6 +8157,19 @@ "dev": true, "license": "ISC" }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/source-map": { "version": "0.1.43", "optional": true, @@ -8090,6 +8461,70 @@ "version": "5.1.2", "license": "MIT" }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/string.prototype.trim": { "version": "1.2.10", "license": "MIT", @@ -8152,6 +8587,46 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/strip-ansi": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.2.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/strip-json-comments": { "version": "3.1.1", "dev": true, @@ -8253,6 +8728,60 @@ "url": "https://opencollective.com/webpack" } }, + "node_modules/test-exclude": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-7.0.2.tgz", + "integrity": "sha512-u9E6A+ZDYdp7a4WnarkXPZOx8Ilz46+kby6p1yZ8zsGTz9gYa6FIS7lj2oezzNKmtdyyJNNmmXDppga5GB7kSw==", + "dev": true, + "license": "ISC", + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^10.4.1", + "minimatch": "^10.2.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/test-exclude/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/test-exclude/node_modules/brace-expansion": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", + "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/test-exclude/node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/through": { "version": "2.3.8", "license": "MIT" @@ -9164,6 +9693,101 @@ "node": ">=0.10.0" } }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/xhr2": { "version": "0.2.1", "license": "MIT", diff --git a/package.json b/package.json index efea3258..1cc6f90d 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,8 @@ "dev": "./scripts/build-docs.sh && vite --mode=${MODE=dev}", "docs": "mkdocs serve -f docs/mkdocs.yml", "preview": "./scripts/build-docs.sh && vite build --mode=${MODE=live} --watch & vite preview --mode=${MODE=live}", - "test:unit": "vitest run" + "test:unit": "vitest run", + "test:unit:coverage": "vitest run --coverage" }, "dependencies": { "@fontsource/exo-2": "^5.2.8", @@ -80,6 +81,7 @@ "@types/uuid": "^9.0.8", "@vitejs/plugin-basic-ssl": "^1.0.2", "@vitejs/plugin-vue": "^6.0.1", + "@vitest/coverage-v8": "2.1.9", "@vue/eslint-config-prettier": "^10.2.0", "@vue/eslint-config-typescript": "^14.6.0", "@vue/tsconfig": "^0.8.1", @@ -92,9 +94,12 @@ "vitest": "^2.1.9", "vue-tsc": "^3.0.7" }, + "optionalDependencies": { + "@rollup/rollup-linux-x64-gnu": "4.46.3" + }, "browserslist": [ "> 1%", "last 2 versions", "not dead" ] -} \ No newline at end of file +} diff --git a/src/assets/app.css b/src/assets/app.css index 2c646737..49b1e8b6 100644 --- a/src/assets/app.css +++ b/src/assets/app.css @@ -121,12 +121,15 @@ --color-ga4gh-light: #e3f0f9; /* ── Measurement Types (variant screen) ────────────────────── */ - --color-nucleotide: var(--color-published); - --color-nucleotide-light: var(--color-published-light); - --color-nucleotide-border: var(--color-light-green); - --color-protein: var(--color-help); - --color-protein-light: var(--color-help-light); - --color-protein-border: var(--color-help-border); + --color-nucleotide: #2e7d32; + --color-nucleotide-light: #e8f5e9; + --color-nucleotide-border: #c8e4c6; + --color-protein: #7e5daf; + --color-protein-light: #f1ecf8; + --color-protein-border: #b5a0d0; + --color-synonymous-nucleotide: #8a5700; + --color-synonymous-nucleotide-light: #fef8e1; + --color-synonymous-nucleotide-border: #f5c97a; /* ── Role Badges ───────────────────────────────────────────── */ --color-role-admin: #1a56a0; diff --git a/src/assets/mave-registry-logo.png b/src/assets/mave-registry-logo.png new file mode 100644 index 00000000..b4c33b8a Binary files /dev/null and b/src/assets/mave-registry-logo.png differ diff --git a/src/components/screens/AboutView.vue b/src/components/screens/AboutView.vue index 2d8c7a01..0612ca29 100644 --- a/src/components/screens/AboutView.vue +++ b/src/components/screens/AboutView.vue @@ -60,9 +60,18 @@ class="h-7 object-contain object-left" :src="partner.logoSrc" /> - -
{{ partner.name }}
+
{{ partner.name }}
{{ partner.description }}
+ + Visit site → + @@ -83,7 +92,7 @@
+ +
+

Clinical application

+

+ The Alliance for Variant Effects (AVE) maintains a + Clinical Application of Variant Effect Data + page covering how MAVE functional scores can be used in clinical genomics and for variant interpretation. +

+
+

Contact us

@@ -89,7 +106,7 @@ import AccordionContent from 'primevue/accordioncontent' import MvLayout from '@/components/layout/MvLayout.vue' import MvPageHeader from '@/components/layout/MvPageHeader.vue' -import {ZULIP_CHAT, GITHUB_UI_ISSUES} from '@/lib/links' +import {ZULIP_CHAT, GITHUB_UI_ISSUES, AVE_CLINICAL_APPLICATION} from '@/lib/links' const CHANNELS = [ { @@ -174,7 +191,8 @@ export default defineComponent({ channels: CHANNELS, faqs: FAQS, ZULIP_CHAT, - GITHUB_UI_ISSUES + GITHUB_UI_ISSUES, + AVE_CLINICAL_APPLICATION } } }) diff --git a/src/components/screens/HomeScreen.vue b/src/components/screens/HomeScreen.vue index 3620f7dc..535dac8c 100644 --- a/src/components/screens/HomeScreen.vue +++ b/src/components/screens/HomeScreen.vue @@ -81,10 +81,12 @@

Find a variant with functional data via HGVS, - ClinGen CAId, - ClinVar ID, - dbSNP rsid, or - VRS digest. + VRS digest, and more. +
+ No variant identifier? + Try guided search →

diff --git a/src/components/screens/SearchVariantsScreen.vue b/src/components/screens/SearchVariantsScreen.vue index 84882b07..07b2eaa7 100644 --- a/src/components/screens/SearchVariantsScreen.vue +++ b/src/components/screens/SearchVariantsScreen.vue @@ -12,6 +12,17 @@ calibrations, provide intuitive visualizations, and export structured evidence compatible with ACMG/AMP variant classification guidelines.

+

+ Learn more about + evaluating MAVE functional assays and integrating them into clinical practice. +

@@ -132,6 +143,7 @@ v-model="inputGene" class="w-full !rounded-none !border-none !bg-transparent !pt-4 !pb-2 !shadow-none" placeholder="e.g. BRCA1" + style="text-transform: uppercase" />
@@ -675,6 +687,7 @@ import {components} from '@/schema/openapi' import {getScoreSetShortName} from '@/lib/score-sets' import {clinVarHgvsSearchStringRegex, hgvsSearchStringRegex} from '@/lib/mave-hgvs' import {SEARCH_COLORS} from '@/data/search' +import {AVE_CLINICAL_APPLICATION} from '@/lib/links' import { HOW_IT_WORKS_STEPS, MAVEMD_COLLECTION_URN, @@ -714,7 +727,7 @@ export default defineComponent({ const router = useRouter() const toast = useToast() const {getEntity} = useEntityCache() - return {route, router, toast, getEntity, getScoreSetShortName, scoreSetUrnFromVariantUrn} + return {route, router, toast, getEntity, getScoreSetShortName, scoreSetUrnFromVariantUrn, AVE_CLINICAL_APPLICATION} }, data: function () { @@ -849,7 +862,14 @@ export default defineComponent({ this.inputVariantType = typeof newVal === 'string' ? newVal : '' } }, - inputGene() { + inputGene(newVal) { + const normalized = newVal?.toUpperCase() ?? null + if (newVal && newVal !== normalized) { + // Setting inputGene re-triggers this watcher with the normalized value, return + // early to avoid syncing query params twice. + this.inputGene = normalized + return + } this.syncGuidedQueryParams() }, inputVariantType(newVal, oldVal) { @@ -992,6 +1012,7 @@ export default defineComponent({ delete query.search delete query.searchType query.mode = 'guided' + this.searchType = 'hgvs' } else { delete query.gene delete query.variantType @@ -1027,8 +1048,8 @@ export default defineComponent({ this.loading = false await this.searchVariants() }, - fetchDefaultSearchResults: async function (searchString: string, maneStatus: string | null = null) { - const searchType = this.searchType + fetchDefaultSearchResults: async function (searchString: string, maneStatus: string | null = null, forcedSearchType?: string) { + const searchType = forcedSearchType ?? this.searchType let searchStr = searchString.trim() try { @@ -1288,7 +1309,7 @@ export default defineComponent({ } for (const hgvsString of hgvsStrings) { - await this.fetchDefaultSearchResults(hgvsString.hgvsString, hgvsString.maneStatus) + await this.fetchDefaultSearchResults(hgvsString.hgvsString, hgvsString.maneStatus, 'hgvs') } } catch (error: unknown) { this.alleles = [] diff --git a/src/components/screens/VariantScreen.vue b/src/components/screens/VariantScreen.vue index 7960af29..14239880 100644 --- a/src/components/screens/VariantScreen.vue +++ b/src/components/screens/VariantScreen.vue @@ -75,6 +75,15 @@ :count="lookup.proteinCount.value" label="Protein level" /> +
diff --git a/src/composables/use-variant-lookup.ts b/src/composables/use-variant-lookup.ts index 6d8ece67..f7b6bd57 100644 --- a/src/composables/use-variant-lookup.ts +++ b/src/composables/use-variant-lookup.ts @@ -45,8 +45,10 @@ export interface UseVariantLookupReturn { // Filters showNucleotide: Ref showProtein: Ref + showAssociatedNucleotide: Ref nucleotideCount: ComputedRef proteinCount: ComputedRef + associatedNucleotideCount: ComputedRef filteredVariants: ComputedRef // Selection @@ -115,19 +117,22 @@ export function useVariantLookup( const selectedVariantUrn = ref(null) const showNucleotide = ref(true) const showProtein = ref(true) + const showAssociatedNucleotide = ref(true) const variantDetailCache = ref>({}) const scoresCache = shallowRef>({}) const selectedCalibration = ref(null) // ── Filters ─────────────────────────────────────────────── - const nucleotideCount = computed( - () => variants.value.filter((v) => v.type === 'nucleotide' || v.type === 'associatedNucleotide').length - ) + const nucleotideCount = computed(() => variants.value.filter((v) => v.type === 'nucleotide').length) const proteinCount = computed(() => variants.value.filter((v) => v.type === 'protein').length) + const associatedNucleotideCount = computed( + () => variants.value.filter((v) => v.type === 'associatedNucleotide').length + ) const filteredVariants = computed(() => variants.value.filter((v) => { - if (v.type === 'nucleotide' || v.type === 'associatedNucleotide') return showNucleotide.value + if (v.type === 'nucleotide') return showNucleotide.value if (v.type === 'protein') return showProtein.value + if (v.type === 'associatedNucleotide') return showAssociatedNucleotide.value return true }) ) @@ -383,8 +388,10 @@ export function useVariantLookup( fetchVariants, showNucleotide, showProtein, + showAssociatedNucleotide, nucleotideCount, proteinCount, + associatedNucleotideCount, filteredVariants, selectedVariantUrn, selectVariant, diff --git a/src/lib/links.ts b/src/lib/links.ts index 84d433cc..26a79c09 100644 --- a/src/lib/links.ts +++ b/src/lib/links.ts @@ -12,6 +12,14 @@ export const GITHUB_API_ISSUES = `${GITHUB_API_URL}/issues` export const MAVEDB_PRODUCTION = 'https://mavedb.org' +export const AVE_CLINICAL_APPLICATION = 'https://www.varianteffect.org/clinical-application/' +export const MAVE_REGISTRY = 'https://registry.varianteffect.org/' + +export const IGVF_URL = 'https://www.igvf.org/' +export const CLINGEN_URL = 'https://clinicalgenome.org/' +export const GA4GH_URL = 'https://www.ga4gh.org/' +export const CLINVAR_URL = 'https://www.ncbi.nlm.nih.gov/clinvar/' + /** * Build a Zulip link to report a dataset issue for a specific URN. */ diff --git a/src/lib/measurement-types.ts b/src/lib/measurement-types.ts index 4e532d16..2d41bf07 100644 --- a/src/lib/measurement-types.ts +++ b/src/lib/measurement-types.ts @@ -3,11 +3,11 @@ export type MeasurementType = 'nucleotide' | 'protein' | 'associatedNucleotide' export const MEASUREMENT_TYPE_LABELS: Record = { nucleotide: {full: 'Nucleotide level', short: 'Nucleotide'}, protein: {full: 'Protein level', short: 'Protein'}, - associatedNucleotide: {full: 'Associated nucleotide', short: 'Assoc. nucleotide'} + associatedNucleotide: {full: 'Synonymous nucleotide', short: 'Synonymous'} } export const MEASUREMENT_TYPE_CLASSES: Record = { nucleotide: 'bg-nucleotide-light text-nucleotide', protein: 'bg-protein-light text-protein', - associatedNucleotide: 'bg-nucleotide-light text-nucleotide' + associatedNucleotide: 'bg-synonymous-nucleotide-light text-synonymous-nucleotide' } diff --git a/src/main.js b/src/main.js index 3635d84f..d22af0d1 100644 --- a/src/main.js +++ b/src/main.js @@ -32,16 +32,28 @@ initializeOrcidAuthentication() // Provide a smooth migration path from vue-router's hash navigation mode to its history navigation mode. If the user // arrived via an old bookmark that uses the URL fragment (hash) for routing, redirect to the corresponding current URL. router.beforeEach((to) => { - const redirectPathAndQuery = to.hash.split('#')[1] - if (redirectPathAndQuery) { - const [redirectPath, redirectQueryStr] = redirectPathAndQuery.split('?', 2) - const redirectQuery = redirectQueryStr ? Object.fromEntries(new URLSearchParams(redirectQueryStr)) : undefined - - // Attempt to resolve the path using vue-router. If the router finds a matching named route, then the path - // represents a valid screen, and we should redirect the user to that screen's current URL. + const regex = /^\/score-sets\/([^#]+)#(\d+)$/ + const match = to.fullPath.match(regex) + if (match) { + const urnBeforeHash = match[1] + const fullUrn = `${urnBeforeHash}#${match[2]}` + const redirectPath = `/score-sets/${urnBeforeHash}` + const redirectQuery = {variant: fullUrn} if (router.resolve({path: redirectPath})?.name) { return {path: redirectPath, query: redirectQuery} } + } else if (to.fullPath.startsWith('/#/')) { + const redirectPathAndQuery = to.fullPath.replace('/#/', '/') + if (redirectPathAndQuery) { + const [redirectPath, redirectQueryStr] = redirectPathAndQuery.split('?', 2) + const redirectQuery = redirectQueryStr ? Object.fromEntries(new URLSearchParams(redirectQueryStr)) : undefined + + // Attempt to resolve the path using vue-router. If the router finds a matching named route, then the path + // represents a valid screen, and we should redirect the user to that screen's current URL. + if (router.resolve({path: redirectPath})?.name) { + return {path: redirectPath, query: redirectQuery} + } + } } }) diff --git a/vitest.config.ts b/vitest.config.ts index a09b0880..8fb3128f 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -9,6 +9,13 @@ export default defineConfig({ } }, test: { - environment: 'node' + environment: 'node', + coverage: { + all: true, + provider: 'v8', + reporter: ['text', 'lcov'], + include: ['src/**/*.{js,ts}'], + exclude: ['src/**/*.d.ts', 'src/**/*.test.{js,ts}'] + } } })