diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ed34571..3a5cdbe 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,7 +7,7 @@ on: branches: [main] jobs: - lint-and-format: + lint: runs-on: ubuntu-latest steps: - uses: actions/checkout@v7 @@ -17,15 +17,51 @@ jobs: cache: 'npm' - run: npm ci + - run: npm run lint - - name: Lint - run: npm run lint + format: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + - uses: actions/setup-node@v6 + with: + node-version: '22' + cache: 'npm' - - name: Format check - run: npm run format:check + - run: npm ci + - run: npm run format:check - - name: Type check - run: npm run compile + type-check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + - uses: actions/setup-node@v6 + with: + node-version: '22' + cache: 'npm' - - name: Test - run: npm test + - run: npm ci + - run: npm run compile + + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + - uses: actions/setup-node@v6 + with: + node-version: '22' + cache: 'npm' + + - run: npm ci + + - name: Test with coverage + run: npm run test:coverage + + - name: Upload coverage to Codecov + if: env.CODECOV_TOKEN != '' + uses: codecov/codecov-action@v5 + with: + files: coverage/lcov.info + fail_ci_if_error: true + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} diff --git a/README.md b/README.md index 96765d8..76a89c9 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,6 @@ # GitHub Webhook Manager +[![codecov](https://codecov.io/gh/seiya-koji/github-webhook-manager/graph/badge.svg)](https://codecov.io/gh/seiya-koji/github-webhook-manager) [![VS Marketplace Version](https://badgen.net/vs-marketplace/v/seiya-koji.github-webhook-manager?label=version)](https://marketplace.visualstudio.com/items?itemName=seiya-koji.github-webhook-manager) [![VS Marketplace Installs](https://badgen.net/vs-marketplace/i/seiya-koji.github-webhook-manager?label=installs)](https://marketplace.visualstudio.com/items?itemName=seiya-koji.github-webhook-manager) [![VS Marketplace Rating](https://badgen.net/vs-marketplace/rating/seiya-koji.github-webhook-manager?label=rating)](https://marketplace.visualstudio.com/items?itemName=seiya-koji.github-webhook-manager) diff --git a/package-lock.json b/package-lock.json index 5332790..4a45e62 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,6 +17,7 @@ "@eslint/js": "^10.0.1", "@types/node": "^20", "@types/vscode": "^1.85.0", + "@vitest/coverage-v8": "^4.1.9", "@vscode/vsce": "^3.0.0", "esbuild": "^0.25.0", "eslint": "^10.5.0", @@ -231,6 +232,16 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/helper-validator-identifier": { "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", @@ -241,6 +252,46 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", + "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.7" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", + "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bcoe/v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz", + "integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/@commitlint/cli": { "version": "21.2.0", "resolved": "https://registry.npmjs.org/@commitlint/cli/-/cli-21.2.0.tgz", @@ -1172,6 +1223,16 @@ "url": "https://github.com/sponsors/nzakas" } }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.5.5", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", @@ -1179,6 +1240,17 @@ "dev": true, "license": "MIT" }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, "node_modules/@napi-rs/wasm-runtime": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.6.tgz", @@ -1421,9 +1493,9 @@ } }, "node_modules/@oxc-project/types": { - "version": "0.137.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.137.0.tgz", - "integrity": "sha512-WT+Gb24i8hmvo85AIv2oEYouEXkRlKAlT9WaCa3TfLgNCN+GhrJOGZuIlMouAh38Qe4QOx26eUOVsq70qXrywA==", + "version": "0.139.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.139.0.tgz", + "integrity": "sha512-r9gHphtCs+1M7J0pw6Sn/hh/Wpa/iQrOOkrNAlVLF/gHq+/CJmHIWKKUUhdWjcD6CIa8idarspCsASiXCXvFUw==", "dev": true, "license": "MIT", "funding": { @@ -1431,9 +1503,9 @@ } }, "node_modules/@rolldown/binding-android-arm64": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.1.3.tgz", - "integrity": "sha512-DT6Z3PhvioeHMvxo+xHc3KtqggrI7CCTXCmC2h/5zUlp5jVitv7XEy+9q5/7v8IolhlioawpMo8Kg0EEBy7J0g==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.1.5.tgz", + "integrity": "sha512-lZg8fqIv2v7FF237bwMgzGZEJvGL79/s5knJ/i6FmsGF4XXlzccZ4jb+TrFIxtSSxFtIpdsgrPZeMk1I9AFcyQ==", "cpu": [ "arm64" ], @@ -1448,9 +1520,9 @@ } }, "node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.1.3.tgz", - "integrity": "sha512-0NwgwsjM7LrsuVnXMK3koTpagBNOhloc/BNjKqZjv4V5zI5r13qx69uVhRx+o5Z0yy4Hzq+lpy7TAgUG/ocvrw==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.1.5.tgz", + "integrity": "sha512-51Bnx9pNiMRKSUNtBfySkNJ9vMU9Hh3I1ozDd6gyPPYzaXCfnptUcEZxXGYFn+ul2dtcMUiqGR1Yai2K10uoTw==", "cpu": [ "arm64" ], @@ -1465,9 +1537,9 @@ } }, "node_modules/@rolldown/binding-darwin-x64": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.1.3.tgz", - "integrity": "sha512-YtiBp4disu6V560loT6PjMdiRaWmVvDNrUunAalbiFx2ggeJwxdAsgZMcoGP17uyAsTwAj5V1niksxlHnVQ1Sw==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.1.5.tgz", + "integrity": "sha512-Tm+gbfC0aHu1tBA/JvKQh32S0K6YgCHkiAF4/W6xX0K0RmNuc94VeK419dJoE65R5aRxmo+noZQSWrAMF6yb6g==", "cpu": [ "x64" ], @@ -1482,9 +1554,9 @@ } }, "node_modules/@rolldown/binding-freebsd-x64": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.1.3.tgz", - "integrity": "sha512-yD3EkEdXk2LypPxnf/kSZHirarsI8gcPzc62SukhR9VJTyvV+F9Q/GxWNuCojc7sXyuVC4DxRGhdDK4X8VSsbw==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.1.5.tgz", + "integrity": "sha512-JMzDKCCXq93YccG5gz3hvOs1oXRKAf0XYpfOS88e+wZrC8Iugj6j68867vrYZkvpDDpKn/KoKORThmchMpF6TA==", "cpu": [ "x64" ], @@ -1499,9 +1571,9 @@ } }, "node_modules/@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.1.3.tgz", - "integrity": "sha512-c+8vieQbsD7HNAHKIA34w0GJ9FedFFuJGD+7E6vz7Q3uqAIugL5p45fhlsj4UaAsHpcmlqugBWMhA0/j7o0sIg==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.1.5.tgz", + "integrity": "sha512-uML21j2K5TfPGutKxub+M+nLjZIrWjXQ5Grx4lCe/nimTj9B4L63zHpjXLl4y0L3mcm2htEQIb06oCG/szerNw==", "cpu": [ "arm" ], @@ -1516,9 +1588,9 @@ } }, "node_modules/@rolldown/binding-linux-arm64-gnu": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.1.3.tgz", - "integrity": "sha512-50jD0uUwLvur7Zz9LHz17kaAdTPjn5wN93hEgjvmYFRZwiR7ZJYovTd5ipyWJDAnXKvZ+wgc+/Ika6dwSF5OcA==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.1.5.tgz", + "integrity": "sha512-navSiuTMogvnQoZoM/v+l3ZWo50/NTwSHSzheABx/RCnmUPaKwq9qSo4Br2OYRs21+Fz8uFqITZM3H4opOB0/Q==", "cpu": [ "arm64" ], @@ -1533,9 +1605,9 @@ } }, "node_modules/@rolldown/binding-linux-arm64-musl": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.1.3.tgz", - "integrity": "sha512-BO9+oPL8K9poZJBfYPsXNtYjPE5uM3qeehT3aFcW4LITOl+iSqhp0abzjR2nWBUNjIZeKXjAEWBZ64WjNoHd6w==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.1.5.tgz", + "integrity": "sha512-lAryqH7IteztmCXQXk0etKj4wBQ7Gx5S6LjKhsgp9zb8I5bsuvU/2llH1hDQcjsFeqIsovMVN339/8pUDDBXxA==", "cpu": [ "arm64" ], @@ -1550,9 +1622,9 @@ } }, "node_modules/@rolldown/binding-linux-ppc64-gnu": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.1.3.tgz", - "integrity": "sha512-f3VpLB1vQ0Eo6ecr/6cekLnvYMFF4YBFoVGkfkvPLq1bAkbAwHYQPZKoAmG6OJyTcxxoC+AvezGx/S1obNC0Mw==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.1.5.tgz", + "integrity": "sha512-fsK/sNBnxzBlL4O1JNrZakVQxPspqpED5dLtNsZS9oOKmtSpdNIzxH2kkol5HYTWJN47sE20ztMJPxfZ89qGOg==", "cpu": [ "ppc64" ], @@ -1567,9 +1639,9 @@ } }, "node_modules/@rolldown/binding-linux-s390x-gnu": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.1.3.tgz", - "integrity": "sha512-AmurZ26Pqx/RI9N1gzEOCklkKXl927yjfXWUUS0O7Puh8ARM/Ob8qfrD3qnWksScdw6cSrW5PSHE9DyLu7+PtA==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.1.5.tgz", + "integrity": "sha512-gLYb4BIadlfTOYT5gO503n8zQjXflgzpD0FcyKh0Mzx3rqCZKnHoJWV9xe1KXUJ5lx2JfcSHr/mhzS0PC/McAA==", "cpu": [ "s390x" ], @@ -1584,9 +1656,9 @@ } }, "node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.1.3.tgz", - "integrity": "sha512-JJpqs8bRGITDOdbkNKnlojzBabbOHrqjSvDr0IVsZObE1lBcPjxItUEY9eWIDbxaJ3cGrXPWGfGkIxFijg/URg==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.1.5.tgz", + "integrity": "sha512-FjcpEKUyJygHgs1o50VYNvkt5+7Le/VEdYt0AkRpkL33MnyQfwr8l5mXwMmfmTbyMPr5vJLC+8/Gd9gXnwU1QQ==", "cpu": [ "x64" ], @@ -1601,9 +1673,9 @@ } }, "node_modules/@rolldown/binding-linux-x64-musl": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.1.3.tgz", - "integrity": "sha512-rSJcdjPxzA/by/6/rYs+v+bXU7UjvnbUWz8MJb6kh6+knqB1dCrtHg0uu7C/4haqJvqdkYHQ5IGn+tCH9GLW/g==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.1.5.tgz", + "integrity": "sha512-Me+PfPI2TMeOQk0gYWfLQZtTktrmzbr8cDboqX83XKc7UrgAi55gF+2dUkWdxd19n55Essp2yeca+O9N5rBxHg==", "cpu": [ "x64" ], @@ -1618,9 +1690,9 @@ } }, "node_modules/@rolldown/binding-openharmony-arm64": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.1.3.tgz", - "integrity": "sha512-hQ3/PYkDJICgevvyNcVrihVeqq7k1Pp3VZ9lY+dauAYUJKO+auqApvANhvR1An9BhmqYKvW2Mu1F9u4DXSMLxQ==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.1.5.tgz", + "integrity": "sha512-yc5WrLzXks6zCQfn9Oxr8pORKyl/pF+QjHmW/Qx3qu0oyrrNC+y2JLTU1E2rcWYAmzlnqngWXHQjy51VzW70Vw==", "cpu": [ "arm64" ], @@ -1635,9 +1707,9 @@ } }, "node_modules/@rolldown/binding-wasm32-wasi": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.1.3.tgz", - "integrity": "sha512-Elcv/BtML9lXrV6JuKITc/grN2kYV9gjsQpW8Jfw4ioK0TOkjBjye0nnyqQNy9STNaI20lXNaQBRrD5gSgR0Yg==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.1.5.tgz", + "integrity": "sha512-VbQGPX2b4r48TAMIM2cjgluIM1HYutm4pcTEJsle7iEP7sB1dFqtPLBVbdLAZCxy1txCcPxf4QFf4v8uvltPqA==", "cpu": [ "wasm32" ], @@ -1654,9 +1726,9 @@ } }, "node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.1.3.tgz", - "integrity": "sha512-2DrEfhluH9yhiaFApmsjsjwrSYbNcY1oFTzYSP1a535jDbV98zCFanA/96TBUd0iDFcxGmw9QRExwGCXz3U+/g==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.1.5.tgz", + "integrity": "sha512-gHv82k63z4qpV5+Q1y/12KrK0ltWBukVDI8nZcbT7Tt/ZlOIVwppazneq0F93oDxTo3IgAMEDIoQh3E2n6mVsw==", "cpu": [ "arm64" ], @@ -1671,9 +1743,9 @@ } }, "node_modules/@rolldown/binding-win32-x64-msvc": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.1.3.tgz", - "integrity": "sha512-OL4OMk7UPXOeVGGd3qo5zJyPIljf4AFgk5QAkPPS+OoLuOOozhuaQGC18MxVTnw/06q93gShAJzlwnSCY9YtqA==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.1.5.tgz", + "integrity": "sha512-tTZuDBPw85tEN5PQi1pnEBzDy0Z49HtScLAbD5t6hyeU92A95pRWaSMw1GZZi/RwgSgUIl0xrSlXIT/9QzvYSA==", "cpu": [ "x64" ], @@ -2340,17 +2412,48 @@ "node": ">=20.0.0" } }, + "node_modules/@vitest/coverage-v8": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.1.10.tgz", + "integrity": "sha512-IM49HmthevbgAO4anp1hwtoT9wYe59w0LR00gr+eagHE+ZJ5lK4sLPeO0ubgoJcwLk6dehU3R24N+FbEEKDc8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@bcoe/v8-coverage": "^1.0.2", + "@vitest/utils": "4.1.10", + "ast-v8-to-istanbul": "^1.0.0", + "istanbul-lib-coverage": "^3.2.2", + "istanbul-lib-report": "^3.0.1", + "istanbul-reports": "^3.2.0", + "magicast": "^0.5.2", + "obug": "^2.1.1", + "std-env": "^4.0.0-rc.1", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@vitest/browser": "4.1.10", + "vitest": "4.1.10" + }, + "peerDependenciesMeta": { + "@vitest/browser": { + "optional": true + } + } + }, "node_modules/@vitest/expect": { - "version": "4.1.9", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.9.tgz", - "integrity": "sha512-vl/rYsUKcBr3SnQn166+XR5ZQcgMx3DQhFWdfli/cWpLnLUmbxZvyrJZotLFUryib+LtArYMSTJ5RbQ57ZqrlA==", + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.10.tgz", + "integrity": "sha512-YsCn+qAk1GWjQOWFEsEcL2gNQ0zmVmQu3T03qP6UyjhtmdtwtbuI+DASn/7iQB3HGTXkdBwGddzxPlmiql5vlA==", "dev": true, "license": "MIT", "dependencies": { "@standard-schema/spec": "^1.1.0", "@types/chai": "^5.2.2", - "@vitest/spy": "4.1.9", - "@vitest/utils": "4.1.9", + "@vitest/spy": "4.1.10", + "@vitest/utils": "4.1.10", "chai": "^6.2.2", "tinyrainbow": "^3.1.0" }, @@ -2359,9 +2462,9 @@ } }, "node_modules/@vitest/pretty-format": { - "version": "4.1.9", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.9.tgz", - "integrity": "sha512-s0iufns3iIFitdgm+YR7g1whCAaGtXz459VS9/PqyKDEEFgYIhsHOQmXgIgDuYCt7DeQmiZT0Qe2OA2p4ZPu5A==", + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.10.tgz", + "integrity": "sha512-W1HsjSH4MXQ9YfmmhLAoIYf1HRfekQCGngeIgcei6MP5QQGWUe0gkopdZQaVCFO+JDJMrAJGwa5pRpNpvy4P8Q==", "dev": true, "license": "MIT", "dependencies": { @@ -2372,13 +2475,13 @@ } }, "node_modules/@vitest/runner": { - "version": "4.1.9", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.9.tgz", - "integrity": "sha512-KXLMDtc7oe70+3mJfGrPUWPesswH+3sTxAMAMl8DG7I8IUQT4XW718dY5ID3vPUcmlu27CcKfY4P3h3I29SLJg==", + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.10.tgz", + "integrity": "sha512-IKI6kpIH+LmpROplyLwBBaCfMgOZOMsygVa6BARD6ahA04VRuJSa6OaVG7kRvSEMD870Vd91rSSw0eegtWyLGg==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/utils": "4.1.9", + "@vitest/utils": "4.1.10", "pathe": "^2.0.3" }, "funding": { @@ -2386,14 +2489,14 @@ } }, "node_modules/@vitest/snapshot": { - "version": "4.1.9", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.9.tgz", - "integrity": "sha512-Jc7RKGNBo8Z28WYIm0Niej4xdSPByRf6mU58VpHQkd6Zh05rlnA+twjbK5HyeIGHxrzsc3mJgS43uM0CZKzaIA==", + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.10.tgz", + "integrity": "sha512-xRkfOT1qpTAi/Ti4Y1LtfRc3kEuqxGw59eN2jN9pRWMtS/XDevekhcFSqvQqjUNGksfjMJu3Y+oJ+4Ypn2OaJw==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "4.1.9", - "@vitest/utils": "4.1.9", + "@vitest/pretty-format": "4.1.10", + "@vitest/utils": "4.1.10", "magic-string": "^0.30.21", "pathe": "^2.0.3" }, @@ -2402,9 +2505,9 @@ } }, "node_modules/@vitest/spy": { - "version": "4.1.9", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.9.tgz", - "integrity": "sha512-fHpsS6mIi+PiEW+vcRVOMkX1oSaPKne3VOclSFICPcGOmfKgXPU5iAah+wcNcj2xPrCCmfq99IDGf+EojhhvhA==", + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.10.tgz", + "integrity": "sha512-PLf/Ugvoq5wO/b4rwYCR1h2PSIdXz7wnkQFMiUpLdtM7l6pqVFcQIBEHyT1+l+cj7mNwAfZHzqXqDyjvOuwbDw==", "dev": true, "license": "MIT", "funding": { @@ -2412,13 +2515,13 @@ } }, "node_modules/@vitest/utils": { - "version": "4.1.9", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.9.tgz", - "integrity": "sha512-A51o8ymO5PpqlWNnBP9ZHPXDIpuMtTLlGSjN7la4US+LJzoUMyhwjA5QXlm39JexgwHKW4Xjs8Z2d3dLCXOeuA==", + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.10.tgz", + "integrity": "sha512-fy9am/HWxbaGt/Sawrp90vt6Y6jQwf1RX77cz3uwoJwJVMli/e1IEwRPnMNJ7vKfPTwo0diXifkpPvwH9v7nGA==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "4.1.9", + "@vitest/pretty-format": "4.1.10", "convert-source-map": "^2.0.0", "tinyrainbow": "^3.1.0" }, @@ -2730,6 +2833,25 @@ "node": ">=12" } }, + "node_modules/ast-v8-to-istanbul": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-1.0.4.tgz", + "integrity": "sha512-0bC0/4bTSrnwdhU3IsZDwEdojvuPrSg59OYZfKsLRtJZ0u8VBx9DebfqqG8bRdCC0I7vjgxmPi41P0lpkhJHtA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.31", + "estree-walker": "^3.0.3", + "js-tokens": "^10.0.0" + } + }, + "node_modules/ast-v8-to-istanbul/node_modules/js-tokens": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-10.0.0.tgz", + "integrity": "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==", + "dev": true, + "license": "MIT" + }, "node_modules/astral-regex": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", @@ -4476,6 +4598,13 @@ "node": ">=10" } }, + "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/htmlparser2": { "version": "10.1.0", "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-10.1.0.tgz", @@ -4787,6 +4916,45 @@ "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-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/istextorbinary": { "version": "9.5.0", "resolved": "https://registry.npmjs.org/istextorbinary/-/istextorbinary-9.5.0.tgz", @@ -5389,6 +5557,34 @@ "@jridgewell/sourcemap-codec": "^1.5.5" } }, + "node_modules/magicast": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.3.tgz", + "integrity": "sha512-pVKE4UdSQ7DvHzivsCIFx2BJn1mHG6KsyrFcaxFx6tONdneEuThrDx0Cj3AMg58KyN4pzYT+LHOotxDQDjNvkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.3", + "@babel/types": "^7.29.0", + "source-map-js": "^1.2.1" + } + }, + "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/markdown-it": { "version": "14.2.0", "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.2.0.tgz", @@ -5581,9 +5777,9 @@ "license": "ISC" }, "node_modules/nanoid": { - "version": "3.3.15", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.15.tgz", - "integrity": "sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA==", + "version": "3.3.16", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz", + "integrity": "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==", "dev": true, "funding": [ { @@ -6028,9 +6224,9 @@ } }, "node_modules/postcss": { - "version": "8.5.16", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.16.tgz", - "integrity": "sha512-vuwillviilfKZsg0VGj5R/YwwcHx4SLsIOI/7K6mQkWx+l5cUHTjj5g0AasTBcyXsbfTgrwsUNmVUb5xVwyPwg==", + "version": "8.5.17", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.17.tgz", + "integrity": "sha512-J7EF+8X+CzRPaJPOv9Ck2wNWJvGnnl3PcNPAdGg6GTLjyVpyQ0yATMSXRFRV01BviT/9Gwuc3rjEyJbDJG9a4w==", "dev": true, "funding": [ { @@ -6331,13 +6527,13 @@ } }, "node_modules/rolldown": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.1.3.tgz", - "integrity": "sha512-1F1eEtUBtFvcGm1HQ9TiUIUHPQG7mSAODrhIzjxoUEFuo8OcbrGLiVLkevNgj84TE4lnHvnumwFjhJO5Eu135g==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.1.5.tgz", + "integrity": "sha512-t9z29cJjXf/vxQ8dyhCSpt6H6aSwHTk8cT5I3iy6SMXuFpk5mB6PL6XfC8PCwrPTx93udwKUm9HRteAlTGBLiA==", "dev": true, "license": "MIT", "dependencies": { - "@oxc-project/types": "=0.137.0", + "@oxc-project/types": "=0.139.0", "@rolldown/pluginutils": "^1.0.0" }, "bin": { @@ -6347,21 +6543,21 @@ "node": "^20.19.0 || >=22.12.0" }, "optionalDependencies": { - "@rolldown/binding-android-arm64": "1.1.3", - "@rolldown/binding-darwin-arm64": "1.1.3", - "@rolldown/binding-darwin-x64": "1.1.3", - "@rolldown/binding-freebsd-x64": "1.1.3", - "@rolldown/binding-linux-arm-gnueabihf": "1.1.3", - "@rolldown/binding-linux-arm64-gnu": "1.1.3", - "@rolldown/binding-linux-arm64-musl": "1.1.3", - "@rolldown/binding-linux-ppc64-gnu": "1.1.3", - "@rolldown/binding-linux-s390x-gnu": "1.1.3", - "@rolldown/binding-linux-x64-gnu": "1.1.3", - "@rolldown/binding-linux-x64-musl": "1.1.3", - "@rolldown/binding-openharmony-arm64": "1.1.3", - "@rolldown/binding-wasm32-wasi": "1.1.3", - "@rolldown/binding-win32-arm64-msvc": "1.1.3", - "@rolldown/binding-win32-x64-msvc": "1.1.3" + "@rolldown/binding-android-arm64": "1.1.5", + "@rolldown/binding-darwin-arm64": "1.1.5", + "@rolldown/binding-darwin-x64": "1.1.5", + "@rolldown/binding-freebsd-x64": "1.1.5", + "@rolldown/binding-linux-arm-gnueabihf": "1.1.5", + "@rolldown/binding-linux-arm64-gnu": "1.1.5", + "@rolldown/binding-linux-arm64-musl": "1.1.5", + "@rolldown/binding-linux-ppc64-gnu": "1.1.5", + "@rolldown/binding-linux-s390x-gnu": "1.1.5", + "@rolldown/binding-linux-x64-gnu": "1.1.5", + "@rolldown/binding-linux-x64-musl": "1.1.5", + "@rolldown/binding-openharmony-arm64": "1.1.5", + "@rolldown/binding-wasm32-wasi": "1.1.5", + "@rolldown/binding-win32-arm64-msvc": "1.1.5", + "@rolldown/binding-win32-x64-msvc": "1.1.5" } }, "node_modules/run-applescript": { @@ -7276,19 +7472,19 @@ } }, "node_modules/vitest": { - "version": "4.1.9", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.9.tgz", - "integrity": "sha512-nE3/LEyc0z87uHYLZebqCUOaJr2hdtuPp7BQ4BosVFnfltxgAvMG08NyrSGlPpOUWvR27c5flSmYFTNr78L9GQ==", + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.10.tgz", + "integrity": "sha512-R9jUTe5S4Qb0HCd4TNqpC7oGcrMssMRGXLW80ubjWsW9VH5GF8y1Y0SFLY9AbqSk6nt0PnOx4H4WNJYZ13GUPw==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/expect": "4.1.9", - "@vitest/mocker": "4.1.9", - "@vitest/pretty-format": "4.1.9", - "@vitest/runner": "4.1.9", - "@vitest/snapshot": "4.1.9", - "@vitest/spy": "4.1.9", - "@vitest/utils": "4.1.9", + "@vitest/expect": "4.1.10", + "@vitest/mocker": "4.1.10", + "@vitest/pretty-format": "4.1.10", + "@vitest/runner": "4.1.10", + "@vitest/snapshot": "4.1.10", + "@vitest/spy": "4.1.10", + "@vitest/utils": "4.1.10", "es-module-lexer": "^2.0.0", "expect-type": "^1.3.0", "magic-string": "^0.30.21", @@ -7316,12 +7512,12 @@ "@edge-runtime/vm": "*", "@opentelemetry/api": "^1.9.0", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", - "@vitest/browser-playwright": "4.1.9", - "@vitest/browser-preview": "4.1.9", - "@vitest/browser-webdriverio": "4.1.9", - "@vitest/coverage-istanbul": "4.1.9", - "@vitest/coverage-v8": "4.1.9", - "@vitest/ui": "4.1.9", + "@vitest/browser-playwright": "4.1.10", + "@vitest/browser-preview": "4.1.10", + "@vitest/browser-webdriverio": "4.1.10", + "@vitest/coverage-istanbul": "4.1.10", + "@vitest/coverage-v8": "4.1.10", + "@vitest/ui": "4.1.10", "happy-dom": "*", "jsdom": "*", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" @@ -7834,13 +8030,13 @@ } }, "node_modules/vitest/node_modules/@vitest/mocker": { - "version": "4.1.9", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.9.tgz", - "integrity": "sha512-EVkXzBjrPGM+cK8/ANWgBrkUCfJfb38/EfTSO8h7pWvKkyPkpWxvR7BkD2MyItMF62C97zAEoqdpUixwR/e+Rw==", + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.10.tgz", + "integrity": "sha512-v0xaezt+DKEmKfaxg133ldzADrwLGd7Ze1MfQQTYfvs8OqZIwbxyxaYURivwV7sWy5fqn3rH5uOrSp07bp44Ow==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "4.1.9", + "@vitest/spy": "4.1.10", "estree-walker": "^3.0.3", "magic-string": "^0.30.21" }, @@ -7905,9 +8101,9 @@ } }, "node_modules/vitest/node_modules/picomatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", "dev": true, "license": "MIT", "engines": { @@ -7918,16 +8114,16 @@ } }, "node_modules/vitest/node_modules/vite": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/vite/-/vite-8.1.1.tgz", - "integrity": "sha512-X/05/cT+VITy2AeDc1der6smvGWWREtL4hPbPTaVbjSBuuWkmNOjR6HP3NzqcQA2nF6VHGUPaFRJyft/2AE9Kg==", + "version": "8.1.4", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.1.4.tgz", + "integrity": "sha512-bTT9PsdWO+MQMNG9ZXIP/qM9wGh37DFxTV/sPq9cFpHr3w4jkgef032PkAL9jAqhk3Nz8NQw3O8n6/xFkqO4QQ==", "dev": true, "license": "MIT", "dependencies": { "lightningcss": "^1.32.0", - "picomatch": "^4.0.4", + "picomatch": "^4.0.5", "postcss": "^8.5.16", - "rolldown": "~1.1.3", + "rolldown": "~1.1.4", "tinyglobby": "^0.2.17" }, "bin": { diff --git a/package.json b/package.json index 2c6c8bf..4fc1f86 100644 --- a/package.json +++ b/package.json @@ -122,6 +122,7 @@ "package": "vsce package --no-dependencies", "prepare": "husky", "test": "vitest run", + "test:coverage": "vitest run --coverage", "lint": "eslint src", "lint:fix": "eslint src --fix", "format": "prettier --write .", @@ -136,6 +137,7 @@ "@eslint/js": "^10.0.1", "@types/node": "^20", "@types/vscode": "^1.85.0", + "@vitest/coverage-v8": "^4.1.9", "@vscode/vsce": "^3.0.0", "esbuild": "^0.25.0", "eslint": "^10.5.0", diff --git a/src/tree/webhookTreeProvider.ts b/src/tree/webhookTreeProvider.ts index 325fd83..1b6e296 100644 --- a/src/tree/webhookTreeProvider.ts +++ b/src/tree/webhookTreeProvider.ts @@ -123,11 +123,12 @@ export class WebhookTreeProvider implements vscode.TreeDataProvider { list.push(delivery); groups.set(branch, list); } - const keys = [...groups.keys()].sort((a, b) => { - if (a === NO_BRANCH_GROUP) return 1; - if (b === NO_BRANCH_GROUP) return -1; - return a.localeCompare(b); - }); - return keys.map((branch) => new BranchNode(hookId, branch, groups.get(branch) ?? [])); + return [...groups.entries()] + .sort(([a], [b]) => { + if (a === NO_BRANCH_GROUP) return 1; + if (b === NO_BRANCH_GROUP) return -1; + return a.localeCompare(b); + }) + .map(([branch, deliveries]) => new BranchNode(hookId, branch, deliveries)); } } diff --git a/tests/errors.test.ts b/tests/errors.test.ts new file mode 100644 index 0000000..b2813c1 --- /dev/null +++ b/tests/errors.test.ts @@ -0,0 +1,17 @@ +import { describe, it, expect } from 'vitest'; +import { toMessage } from '../src/errors'; + +describe('toMessage', () => { + it('returns the message of an Error instance', () => { + expect(toMessage(new Error('boom'))).toBe('boom'); + }); + + it('returns a string value unchanged', () => { + expect(toMessage('plain failure')).toBe('plain failure'); + }); + + it('JSON-stringifies any other thrown value', () => { + expect(toMessage({ code: 42 })).toBe('{"code":42}'); + expect(toMessage(123)).toBe('123'); + }); +}); diff --git a/tests/extension.test.ts b/tests/extension.test.ts index c8f345b..9af6977 100644 --- a/tests/extension.test.ts +++ b/tests/extension.test.ts @@ -184,6 +184,113 @@ describe('activate', () => { expect(vscode.window.showWarningMessage).toHaveBeenCalled(); }); + it('shows delivery details in a webview panel', async () => { + repos.getWebhookDelivery.mockResolvedValue({ + data: { + id: 10, + guid: 'g', + event: 'push', + action: null, + status: 'OK', + status_code: 200, + duration: 5, + redelivery: false, + delivered_at: 'x', + request: { headers: null, payload: null }, + response: { headers: null, payload: null }, + }, + }); + activate(makeContext()); + await flush(); + + await handlerFor('githubWebhooks.showDelivery')(deliveryNode); + + expect(repos.getWebhookDelivery).toHaveBeenCalledWith({ + owner: 'seiya-koji', + repo: 'demo', + hook_id: 7, + delivery_id: 10, + }); + expect(vscode.window.createWebviewPanel).toHaveBeenCalled(); + }); + + it('surfaces an error when loading delivery details fails', async () => { + repos.getWebhookDelivery.mockRejectedValue(new Error('nope')); + activate(makeContext()); + await flush(); + + await handlerFor('githubWebhooks.showDelivery')(deliveryNode); + + expect(vscode.window.showErrorMessage).toHaveBeenCalled(); + }); + + it('ignores showDelivery when no client is active', async () => { + execFile.mockImplementation((_c: string, _a: string[], _o: unknown, cb: ExecCb) => + cb(new Error('not a git repository'), '') + ); + activate(makeContext()); + await flush(); + + await handlerFor('githubWebhooks.showDelivery')(deliveryNode); + + expect(repos.getWebhookDelivery).not.toHaveBeenCalled(); + }); + + it('ignores redeliver when no client is active', async () => { + execFile.mockImplementation((_c: string, _a: string[], _o: unknown, cb: ExecCb) => + cb(new Error('not a git repository'), '') + ); + activate(makeContext()); + await flush(); + + await handlerFor('githubWebhooks.redeliver')(deliveryNode); + + expect(vscode.window.showWarningMessage).not.toHaveBeenCalled(); + }); + + it('surfaces an error when redelivery fails', async () => { + vi.mocked(vscode.window.showWarningMessage).mockResolvedValue( + 'Redeliver' as unknown as vscode.MessageItem + ); + repos.redeliverWebhookDelivery.mockRejectedValue(new Error('boom')); + activate(makeContext()); + await flush(); + + await handlerFor('githubWebhooks.redeliver')(deliveryNode); + + expect(vscode.window.showErrorMessage).toHaveBeenCalled(); + }); + + it('ignores openWebhookSettings when the node carries no webhook', async () => { + activate(makeContext()); + await flush(); + + await handlerFor('githubWebhooks.openWebhookSettings')({} as unknown as WebhookNode); + + expect(vscode.env.openExternal).not.toHaveBeenCalled(); + }); + + it('surfaces an error when authentication fails during load', async () => { + vi.mocked(vscode.authentication.getSession).mockRejectedValue(new Error('auth denied')); + activate(makeContext()); + await flush(); + await flush(); + + expect(vscode.window.showErrorMessage).toHaveBeenCalled(); + }); + + it('refreshes the tree when the refresh command runs', async () => { + activate(makeContext()); + await flush(); + const options = vi.mocked(vscode.window.createTreeView).mock.calls[0][1]; + const provider = options.treeDataProvider as unknown as { refresh: () => void }; + const spy = vi.spyOn(provider, 'refresh'); + + await handlerFor('githubWebhooks.refresh')(); + + expect(spy).toHaveBeenCalled(); + }); + it('deactivate runs without throwing', () => { expect(() => deactivate()).not.toThrow(); }); diff --git a/tests/github/repoResolver.test.ts b/tests/github/repoResolver.test.ts index 00aa161..35f79f7 100644 --- a/tests/github/repoResolver.test.ts +++ b/tests/github/repoResolver.test.ts @@ -132,6 +132,18 @@ describe('resolveRepo', () => { expect(await resolveRepo()).toBeUndefined(); expect(execFile).not.toHaveBeenCalled(); }); + + it('returns undefined when workspaceFolders is undefined', async () => { + setFolders(undefined); + expect(await resolveRepo()).toBeUndefined(); + expect(execFile).not.toHaveBeenCalled(); + }); + + it('returns undefined when git succeeds but no remote parses to a GitHub repo', async () => { + setFolders(DEFAULT_FOLDERS); + gitReturns('origin\tsome-unparseable-remote (fetch)\n'); + expect(await resolveRepo()).toBeUndefined(); + }); }); describe('webhookSettingsUrl', () => { diff --git a/tests/panel/deliveryPanel.test.ts b/tests/panel/deliveryPanel.test.ts index 68f642f..23482c8 100644 --- a/tests/panel/deliveryPanel.test.ts +++ b/tests/panel/deliveryPanel.test.ts @@ -1,5 +1,11 @@ -import { describe, it, expect } from 'vitest'; -import { escapeHtml, formatPayload, renderHtml } from '../../src/panel/deliveryPanel'; +import { describe, it, expect, vi } from 'vitest'; +import * as vscode from 'vscode'; +import { + DeliveryPanel, + escapeHtml, + formatPayload, + renderHtml, +} from '../../src/panel/deliveryPanel'; import type { DeliveryDetail } from '../../src/github/webhookClient'; describe('escapeHtml', () => { @@ -67,4 +73,42 @@ describe('renderHtml', () => { ); expect(html).not.toContain('Branch:'); }); + + it('includes the action and redelivery flag in the metadata line', () => { + const html = renderHtml(detail({ action: 'opened', redelivery: true })); + expect(html).toContain('push.opened'); + expect(html).toContain('redelivery'); + }); +}); + +describe('DeliveryPanel', () => { + it('creates a panel, reuses it on later shows, and recreates after disposal', () => { + let disposeCb: () => void = () => {}; + const panel = { + title: '', + webview: { html: '' }, + reveal: vi.fn(), + onDidDispose: vi.fn((cb: () => void) => { + disposeCb = cb; + }), + dispose: vi.fn(), + }; + const create = vi.mocked(vscode.window.createWebviewPanel); + create.mockReturnValue(panel as unknown as ReturnType); + create.mockClear(); + + DeliveryPanel.show(detail({ id: 1 })); + expect(create).toHaveBeenCalledTimes(1); + expect(panel.title).toBe('Delivery #1'); + expect(panel.webview.html).toContain('Webhook delivery #1'); + + DeliveryPanel.show(detail({ id: 2 })); + expect(create).toHaveBeenCalledTimes(1); // reused, not recreated + expect(panel.reveal).toHaveBeenCalledTimes(1); + expect(panel.webview.html).toContain('Webhook delivery #2'); + + disposeCb(); // clears the singleton + DeliveryPanel.show(detail({ id: 3 })); + expect(create).toHaveBeenCalledTimes(2); + }); }); diff --git a/tests/tree/nodes.test.ts b/tests/tree/nodes.test.ts index 6f8f928..00d7afa 100644 --- a/tests/tree/nodes.test.ts +++ b/tests/tree/nodes.test.ts @@ -73,6 +73,18 @@ describe('DeliveryNode', () => { expect(node.description).toContain('main'); expect(node.tooltip).toContain('Branch: main'); }); + + it('uses the error icon for a failed status and includes the action in the tooltip', () => { + const node = new DeliveryNode(7, { + ...delivery, + status_code: 500, + status: 'Internal Server Error', + action: 'opened', + }); + expect((node.iconPath as { id: string }).id).toBe('error'); + expect((node.iconPath as { color: { id: string } }).color.id).toBe('testing.iconFailed'); + expect(node.tooltip).toContain('push.opened'); + }); }); describe('BranchNode', () => { diff --git a/tests/tree/webhookTreeProvider.test.ts b/tests/tree/webhookTreeProvider.test.ts index a9ed772..b96a6f3 100644 --- a/tests/tree/webhookTreeProvider.test.ts +++ b/tests/tree/webhookTreeProvider.test.ts @@ -1,5 +1,5 @@ import { describe, it, expect, vi } from 'vitest'; -import type * as vscode from 'vscode'; +import * as vscode from 'vscode'; import { WebhookTreeProvider } from '../../src/tree/webhookTreeProvider'; import { HostNode, WebhookNode, BranchNode, DeliveryNode, MessageNode } from '../../src/tree/nodes'; import type { WebhookClient } from '../../src/github/webhookClient'; @@ -250,4 +250,99 @@ describe('WebhookTreeProvider', () => { expect(labels).toEqual(['(No branch)']); // all deliveries fall back to no-branch group warn.mockRestore(); }); + + it('sorts branch groups alphabetically with the branchless group last', async () => { + const deliveries = [ + summary(20, 'push', null), + summary(21, 'issues', 'opened'), + summary(22, 'push', null), + summary(23, 'pull_request', 'opened'), + ]; + const payloads: Record = { + 20: { ref: 'refs/heads/zeta' }, + 21: { issue: { number: 1 } }, + 22: { ref: 'refs/heads/alpha' }, + 23: { pull_request: { base: { ref: 'mu' } } }, + }; + const client = { + listWebhooks: vi + .fn() + .mockResolvedValue([ + { id: 1, name: 'web', active: true, events: ['push'], config: { url: 'https://h/hook' } }, + ]), + listDeliveries: vi.fn().mockResolvedValue(deliveries), + getDelivery: vi + .fn() + .mockImplementation((_hookId: number, id: number) => + Promise.resolve({ id, request: { payload: payloads[id] } }) + ), + } as unknown as WebhookClient; + const provider = new WebhookTreeProvider(makeLog()); + provider.setClient(client); + + const webhookNode = await getWebhookNode(provider); + const branches = await provider.getChildren(webhookNode); + + expect(branches.map((node) => node.label)).toEqual(['alpha', 'mu', 'zeta', '(No branch)']); + }); + + it('returns the element itself as its tree item', () => { + const provider = new WebhookTreeProvider(makeLog()); + const node = new MessageNode('hello'); + expect(provider.getTreeItem(node)).toBe(node); + }); + + it('groups a webhook whose config has no url under its name', async () => { + const provider = new WebhookTreeProvider(makeLog()); + provider.setClient({ + listWebhooks: vi + .fn() + .mockResolvedValue([ + { id: 1, name: 'named-hook', active: true, events: ['push'], config: {} }, + ]), + listDeliveries: vi.fn(), + } as unknown as WebhookClient); + + const roots = await provider.getChildren(); + + expect(roots).toHaveLength(1); + expect(roots[0].label).toBe('named-hook'); + }); + + it('returns no children for node types that never expand', async () => { + const provider = new WebhookTreeProvider(makeLog()); + provider.setClient(createClient()); + + expect(await provider.getChildren(new MessageNode('leaf'))).toEqual([]); + }); + + it('reports an error and returns no children when listing webhooks fails', async () => { + const log = makeLog(); + const provider = new WebhookTreeProvider(log); + provider.setClient({ + listWebhooks: vi.fn().mockRejectedValue(new Error('api down')), + listDeliveries: vi.fn(), + } as unknown as WebhookClient); + + const roots = await provider.getChildren(); + + expect(roots).toEqual([]); + expect(log.error).toHaveBeenCalled(); + expect(vscode.window.showErrorMessage).toHaveBeenCalled(); + }); + + it('returns an ungrouped delivery list when no cache is present', async () => { + const provider = new WebhookTreeProvider(makeLog()); + const client = createBranchClient(); + // White-box: bypass setClient so no DeliveryCache is created, exercising the + // ungrouped fallback in webhookChildren. + (provider as unknown as { client: WebhookClient }).client = client; + + const webhookNode = await getWebhookNode(provider); + const children = await provider.getChildren(webhookNode); + + expect(children.every((node) => node instanceof DeliveryNode)).toBe(true); + expect(children).toHaveLength(3); + expect(client.getDelivery).not.toHaveBeenCalled(); + }); }); diff --git a/vitest.config.ts b/vitest.config.ts index 0058bc2..81809ba 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -11,5 +11,17 @@ export default defineConfig({ globals: true, environment: 'node', include: ['tests/**/*.test.ts'], + coverage: { + provider: 'v8', + reporter: ['text', 'lcov'], + reportsDirectory: './coverage', + include: ['src/**/*.ts'], + thresholds: { + statements: 100, + branches: 100, + functions: 100, + lines: 100, + }, + }, }, });