diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 10b7670..9d256b9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,9 +23,35 @@ jobs: run: sudo apt-get update && sudo apt-get install -y xvfb - name: Lint run: npm run lint - - name: Compile TypeScript + - name: Compile Extension run: npm run compile + - name: Compile Integration Tests + run: npm run compile:tests - name: Run unit tests run: npm run test:unit - - name: Run extension tests - run: xvfb-run -a npm test + - name: Generate unit coverage reports + run: npm run coverage:unit + - name: Run integration tests + run: xvfb-run -a npm run test:integration + - name: Generate integration coverage reports + run: xvfb-run -a npm run coverage:integration + - name: List coverage files + run: | + echo "Unit coverage files:" + ls -l ./coverage/unit + echo "Integration coverage files:" + ls -l ./coverage/integration + - name: Upload unit coverage + uses: codecov/codecov-action@v4 + with: + token: ${{ secrets.CODECOV_TOKEN }} + files: ./coverage/unit/lcov.info + flags: unit + disable_search: true + - name: Upload integration coverage + uses: codecov/codecov-action@v4 + with: + token: ${{ secrets.CODECOV_TOKEN }} + files: ./coverage/integration/lcov.info + flags: integration + disable_search: true diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index d9ca8ca..c216bfe 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -4,9 +4,6 @@ on: push: branches: - master -permissions: - contents: write - jobs: publish: runs-on: ubuntu-latest @@ -25,16 +22,8 @@ jobs: - name: List packaged files run: npx vsce ls - - name: Configure Git - run: | - git config --global user.name "github-actions[bot]" - git config --global user.email "github-actions[bot]@users.noreply.github.com" - - - name: Version bump - run: | - npm version patch - git push - git push --tags + - name: Update version + run: npm version 0.0.${{ github.run_number }} --no-git-tag-version - name: Publish to VS Code Marketplace run: npx vsce publish patch diff --git a/.gitignore b/.gitignore index 0b60dfa..9319372 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,5 @@ dist node_modules .vscode-test/ *.vsix +coverage +*.log \ No newline at end of file diff --git a/.vscode-test.mjs b/.vscode-test.mjs index b62ba25..bc51c27 100644 --- a/.vscode-test.mjs +++ b/.vscode-test.mjs @@ -1,5 +1,27 @@ -import { defineConfig } from '@vscode/test-cli'; +import { defineConfig } from "@vscode/test-cli"; +import * as fs from "fs/promises"; +import * as path from "path"; +import * as os from "os"; +import simpleGit from "simple-git"; + +const fixturePath = path.join(os.tmpdir(), "gops-integration-test-workspace"); + +/** Initialise a minimal git repo so `GitService` (extension activate) finds a workspace. */ +async function prepareWorkspace() { + await fs.rm(fixturePath, { recursive: true, force: true }).catch(() => {}); + await fs.mkdir(fixturePath, { recursive: true }); + const git = simpleGit(fixturePath); + await git.init(); + await git.addConfig("user.email", "test@example.com"); + await git.addConfig("user.name", "Test User"); + await fs.writeFile(path.join(fixturePath, "README.md"), "# Test\n"); + await git.add("README.md"); + await git.commit("Initial commit"); +} + +await prepareWorkspace(); export default defineConfig({ - files: 'out/test/**/*.test.js', + files: "out/test/integration/**/*.test.js", + workspaceFolder: fixturePath, }); diff --git a/README.md b/README.md index 61d3bc1..7d8db4f 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,10 @@ Git Operations - Visual Git Toolkit for VS Code [![CI Builds](https://github.com/thedev-codeman/gops/actions/workflows/ci.yml/badge.svg?branch=develop)](https://github.com/thedev-codeman/gops/actions/workflows/ci.yml) +[![Unit Coverage](https://codecov.io/gh/codemanxdev/gops/branch/develop/graph/badge.svg?flag=unit)](https://codecov.io/gh/codemanxdev/gops) + +[![Integration Coverage](https://codecov.io/gh/codemanxdev/gops/branch/develop/graph/badge.svg?flag=integration)](https://codecov.io/gh/codemanxdev/gops) + ## Features ### Tree View diff --git a/package-lock.json b/package-lock.json index c4d9c5d..1a8ba0d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "gops", - "version": "0.0.4", + "version": "0.0.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "gops", - "version": "0.0.4", + "version": "0.0.1", "dependencies": { "simple-git": "^3.36.0" }, @@ -14,7 +14,8 @@ "@types/mocha": "^10.0.10", "@types/node": "^22.19.19", "@types/vscode": "^1.110.0", - "@vscode/test-cli": "^0.0.11", + "@vitest/coverage-v8": "^4.1.7", + "@vscode/test-cli": "^0.0.12", "@vscode/test-electron": "^2.5.2", "esbuild": "^0.27.3", "eslint": "^9.39.3", @@ -28,12 +29,65 @@ "vscode": "^1.110.0" } }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.3.tgz", + "integrity": "sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "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==", + "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" + "license": "MIT", + "engines": { + "node": ">=18" + } }, "node_modules/@emnapi/core": { "version": "1.10.0", @@ -774,9 +828,9 @@ } }, "node_modules/@istanbuljs/schema": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", + "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": { @@ -846,9 +900,9 @@ } }, "node_modules/@oxc-project/types": { - "version": "0.129.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.129.0.tgz", - "integrity": "sha512-3oz8m3FGdr2nDXVqmFUw7jolKliC4MoyXYIG2c7gpjBnzUWQpUGIYcXYKxTdTi+N2jusvt610ckTMkxdwHkYEg==", + "version": "0.130.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.130.0.tgz", + "integrity": "sha512-ibD2usx9JRu7f5pu2tMKMI4cpA4NgXJQoYRP4pQ7Pxmn1l6k/53qWtQWZayhYy3X4QZkt90Ot+mJEaeXouio6Q==", "dev": true, "license": "MIT", "funding": { @@ -867,9 +921,9 @@ } }, "node_modules/@rolldown/binding-android-arm64": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0.tgz", - "integrity": "sha512-TWMZnRLMe63C2Lhyicviu7ZHaU4kxa6PS3rofvc9GmcvptzNN11BcfQ4Sl7MwTOsisQoa2keB/EBdNCAnUo8vA==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.1.tgz", + "integrity": "sha512-fJI3I0r3C3Oj/zdBCpaCmBRZYf07xpaq4yCfDDoSFm+beWNzbIl26puW8RraUdugoJw/95zerNOn6jasAhzSmg==", "cpu": [ "arm64" ], @@ -884,9 +938,9 @@ } }, "node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0.tgz", - "integrity": "sha512-6XcD+8k0gPVItNagEw78/qqcBDwKcwDYS8V2hRmVsfUSIrd8cWe/CBvRDI5toqFyPfj+FJr6t8U6Xj2P2prEew==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.1.tgz", + "integrity": "sha512-cKnAhWEsV7TPcA/5EAteDp6KcJZBQ2G+BqE7zayMMi7kMvwRsbv7WT9aOnn0WNl4SKEIf43vjS31iUPu80nzXg==", "cpu": [ "arm64" ], @@ -901,9 +955,9 @@ } }, "node_modules/@rolldown/binding-darwin-x64": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0.tgz", - "integrity": "sha512-iN/tWVXRQDWvmZlKdceP1Dwug9GDpEymhb9p4xnEe6zvCg5lFmzVljl+1qR1NVx3yfGpr2Na+CuLmv5IU8uzfQ==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.1.tgz", + "integrity": "sha512-YKrVwQjIRBPo+5G/u03wGjbdy4q7pyzCe93DK9VJ7zkVmeg8LJ7GbgsiHWdR4xSoe4CAXRD7Bcjgbtr64bkXNg==", "cpu": [ "x64" ], @@ -918,9 +972,9 @@ } }, "node_modules/@rolldown/binding-freebsd-x64": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0.tgz", - "integrity": "sha512-jjQMDvvwSOuhOwMszD/klSOjyWMM3zI64hWTj9KT5x4MxRbZAf+7vLQ6qouRhtsLVFHr3f0ILaJAfgENPiQdAQ==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.1.tgz", + "integrity": "sha512-z/oBsREo46SsFqBwYtFe0kpJeBijAT48O/WXLI4suiCLBkr03RTtTJMCzSdDd2znlh8VJizL09XVkQgk8IZonw==", "cpu": [ "x64" ], @@ -935,9 +989,9 @@ } }, "node_modules/@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0.tgz", - "integrity": "sha512-d//Dtg2x6/m3mbV64yUGNnDGNZaDGRpDLLNGerHQUVObuNaIQaaDp25yUiqGXtHEXX+NP2d0wAlmKgpYgIAJ2A==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.1.tgz", + "integrity": "sha512-ik8q7GM11zxvYxFc2PeDcT6TBvhCQMaUxfph/M5l9sKuTs/Sjg3L+Byw0F7w0ZVLBZmx30P+gG0ECzzN+MFcmQ==", "cpu": [ "arm" ], @@ -952,13 +1006,16 @@ } }, "node_modules/@rolldown/binding-linux-arm64-gnu": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0.tgz", - "integrity": "sha512-n7Ofp0mx+aB2cC+Sdy5YtMnXtY9lchnHbY+3Yt0uq9JsWQExf4f5Whu0tK0R8Jdc9S6RchTHjIFY7uc92puOVQ==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.1.tgz", + "integrity": "sha512-QoSx2EkyrrdZ6kcyE8stqZ62t0Yra8Fs5ia9lOxJrh6TMQJK7gQKmscdTHf7pOXKREKrVwOtJcQG3qVSfc866A==", "cpu": [ "arm64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -969,13 +1026,16 @@ } }, "node_modules/@rolldown/binding-linux-arm64-musl": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0.tgz", - "integrity": "sha512-EIVjy2cgd7uuMMo94FVkBp7F6DhcZAUwNURkSG3RwUmvAXR6s0ISxM81U+IydcZByPG0pZIHsf1b6kTxoFDgJA==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.1.tgz", + "integrity": "sha512-uwNwFpwKeNiZawfAWBgg0VIztPTV3ihhh1vV334h9ivnNLorxnQMU6Fz8wG1Zb4Qh9LC1/MkcyT3YlDXG3Rsgg==", "cpu": [ "arm64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -986,13 +1046,16 @@ } }, "node_modules/@rolldown/binding-linux-ppc64-gnu": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0.tgz", - "integrity": "sha512-JEwwOPcwTLAcpDQlqSmjEmfs63xJnSiUNIGvLcDLUHCWK4XowpS/7c7tUsUH6uT/ct6bMUTdXKfI8967FYj6mg==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.1.tgz", + "integrity": "sha512-zY1bul7OWr7DFBiJ++wofXvnr8B45ce3QsQUhKrIhXsygAh7bTkwyeM1bi1a2g5C/yC/N8TZyGDEoMfm/l9mpg==", "cpu": [ "ppc64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -1003,13 +1066,16 @@ } }, "node_modules/@rolldown/binding-linux-s390x-gnu": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0.tgz", - "integrity": "sha512-0wjCFhLrihtAubnT9iA0N++0pSV0z5Hg7tNGdNJ4RFaINceHadoF+kiFGyY1qSSNVIAZtLotG8Ju1bgDPkjnFA==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.1.tgz", + "integrity": "sha512-0frlsT/f4Ft6I7SMESTKnF3cZsdicQn1dCMkF/jT9wDLE+gGoiQfv1nmT9e+s7s/fekvvy6tZM2jHvI2tkbJDQ==", "cpu": [ "s390x" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -1020,13 +1086,16 @@ } }, "node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0.tgz", - "integrity": "sha512-Dfn7iak9BcMMePxcoJfpSbWqnEyrp/dRF63/8qW/eHBdOZov6x5aShLLEYGYdIeSJ6vMLK/XCVB+lGIxm41bQA==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.1.tgz", + "integrity": "sha512-XABVmGp9Tg0WspTVvwduTc4fpqy6JnAUrSQe6OuyqD/03nI7r0O9OWUkMIwFrjKAIqolvqoA4ZrJppgwE0Gxmw==", "cpu": [ "x64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -1037,13 +1106,16 @@ } }, "node_modules/@rolldown/binding-linux-x64-musl": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0.tgz", - "integrity": "sha512-5/utzzDmD/pD/bmuaUcbTf/sZYy0aztwIVlfpoW1fTjCZ0BaPOMVWGZL1zvgxyi7ZIVYWlxKONHmSbHuiOh8Jw==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.1.tgz", + "integrity": "sha512-bV4fzswuzVcKD90o/VM6QqKxnxlDq0g2BISDLNVmxrnhpv1DDbyPhCIjYfvzYLV+MvkKKnQt2Q6AO86SEBULUQ==", "cpu": [ "x64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -1054,9 +1126,9 @@ } }, "node_modules/@rolldown/binding-openharmony-arm64": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0.tgz", - "integrity": "sha512-ouJs8VcUomfLfpbUECqFMRqdV4x6aeAK3MA4m6vTrJJjKyWTV5KnxZx7Jd9G+GlDaQQxubcba00x16OyJ1meig==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.1.tgz", + "integrity": "sha512-/Mh0Zhq3OP7fVs0kcQHZP6lZEthMGTaSf8UBQYSFEZDWGXXlEC+nJ6EqenaK2t4LBXMe3A+K/G2BVXXdtOr4PQ==", "cpu": [ "arm64" ], @@ -1071,9 +1143,9 @@ } }, "node_modules/@rolldown/binding-wasm32-wasi": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0.tgz", - "integrity": "sha512-E+oHKGiDA+lsKMmFtffDDw91EryDT7uJocrIuCHqhm6bCTM6xFK+3gaCkYOHfPwQr0cCNarSM2xaELoQDz9jJg==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.1.tgz", + "integrity": "sha512-+1xc9X45l8ufsBAm6Gjvx2qDRIY9lTVt0cgWNcJ+1gdhXvkbxePA60yRTwSTuXL09CMhyJmjpV7E3NoyxbqFQQ==", "cpu": [ "wasm32" ], @@ -1090,9 +1162,9 @@ } }, "node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0.tgz", - "integrity": "sha512-yYK02n8Rngo+gbm1y6G0+7jk1sJ/2Wt7K0me0Y7k/ErBpyf+LJ2gFpqWVTcRV1rUepBlQRmpgWkTQCiiwrK0Ow==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.1.tgz", + "integrity": "sha512-1D+UqZdfnuR+Jy1GgMJwi85bD40H21uNmOPRWQhw4oRSuolZ/B5rixZ45DK2KXOTCvmVCecauWgEhbw8bI7tOw==", "cpu": [ "arm64" ], @@ -1107,9 +1179,9 @@ } }, "node_modules/@rolldown/binding-win32-x64-msvc": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0.tgz", - "integrity": "sha512-14bpChMahXRRXiTwahSl+zzHPW6qQTXtkMuJBFlbo+pqSAews2d4BdCSHfrJ/MBsCZtpmTafsY+1QhBzitcmdg==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.1.tgz", + "integrity": "sha512-INAycaWuhlOK3wk4mRHGsdgwYWmd9cChdPdE9bwWmy6rn9VqVNYNFGhOdXrofXUxwHIncSiPNb8tNm8knDVIeQ==", "cpu": [ "x64" ], @@ -1124,9 +1196,9 @@ } }, "node_modules/@rolldown/pluginutils": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0.tgz", - "integrity": "sha512-aKs/3GSWyV0mrhNmt/96/Z3yczC3yvrzYATCiCXQebBsGyYzjNdUphRVLeJQ67ySKVXRfMxt2lm12pmXvbPFQQ==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", "dev": true, "license": "MIT" }, @@ -1508,17 +1580,48 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/@vitest/coverage-v8": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.1.7.tgz", + "integrity": "sha512-qsYPeXc5Q9dFLd1i8Ap+Bx8sQgcp+rFVQo4R0dDsWNBzl26ldVF1qOO+RL24K7FDrR6pA+50XedRLSoSG24bVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@bcoe/v8-coverage": "^1.0.2", + "@vitest/utils": "4.1.7", + "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.7", + "vitest": "4.1.7" + }, + "peerDependenciesMeta": { + "@vitest/browser": { + "optional": true + } + } + }, "node_modules/@vitest/expect": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.5.tgz", - "integrity": "sha512-PWBaRY5JoKuRnHlUHfpV/KohFylaDZTupcXN1H9vYryNLOnitSw60Mw9IAE2r67NbwwzBw/Cc/8q9BK3kIX8Kw==", + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.7.tgz", + "integrity": "sha512-1R+tw0ortHEbZDGMymm+pN7/AFQ/RkFFdtd7EN+VBpynKmLbP8A3rpEXdshBJ7+8hQ9zBJh/i1s0yKNtxAnU7w==", "dev": true, "license": "MIT", "dependencies": { "@standard-schema/spec": "^1.1.0", "@types/chai": "^5.2.2", - "@vitest/spy": "4.1.5", - "@vitest/utils": "4.1.5", + "@vitest/spy": "4.1.7", + "@vitest/utils": "4.1.7", "chai": "^6.2.2", "tinyrainbow": "^3.1.0" }, @@ -1527,13 +1630,13 @@ } }, "node_modules/@vitest/mocker": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.5.tgz", - "integrity": "sha512-/x2EmFC4mT4NNzqvC3fmesuV97w5FC903KPmey4gsnJiMQ3Be1IlDKVaDaG8iqaLFHqJ2FVEkxZk5VmeLjIItw==", + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.7.tgz", + "integrity": "sha512-vY7nuamKgfvpA1Koa3oYIw/k7D6kZnpGyNMZW8loow2bsBYla1TFdqTaXncWdRn4pgwNs+90RhnXhJScDwQeJA==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/spy": "4.1.5", + "@vitest/spy": "4.1.7", "estree-walker": "^3.0.3", "magic-string": "^0.30.21" }, @@ -1554,9 +1657,9 @@ } }, "node_modules/@vitest/pretty-format": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.5.tgz", - "integrity": "sha512-7I3q6l5qr03dVfMX2wCo9FxwSJbPdwKjy2uu/YPpU3wfHvIL4QHwVRp57OfGrDFeUJ8/8QdfBKIV12FTtLn00g==", + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.7.tgz", + "integrity": "sha512-umgCarTOYQWIaDMvGDRZij+6b9oVeLIyJzfN+AS88e0ZOU3QTgNNSTtjQOpcvWr3np1N0j4WgZj+sb3oYBDscw==", "dev": true, "license": "MIT", "dependencies": { @@ -1567,13 +1670,13 @@ } }, "node_modules/@vitest/runner": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.5.tgz", - "integrity": "sha512-2D+o7Pr82IEO46YPpoA/YU0neeyr6FTerQb5Ro7BUnBuv6NQtT/kmVnczngiMEBhzgqz2UZYl5gArejsyERDSQ==", + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.7.tgz", + "integrity": "sha512-BapjmAQ2aI78WdMEfeUWivnfVzB+VPGwWRQcJE0OUq7qEeEcBsCSf+0T5iREBNE5nBb4wA5Ya0W6IA+sghdEFw==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/utils": "4.1.5", + "@vitest/utils": "4.1.7", "pathe": "^2.0.3" }, "funding": { @@ -1581,14 +1684,14 @@ } }, "node_modules/@vitest/snapshot": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.5.tgz", - "integrity": "sha512-zypXEt4KH/XgKGPUz4eC2AvErYx0My5hfL8oDb1HzGFpEk1P62bxSohdyOmvz+d9UJwanI68MKwr2EquOaOgMQ==", + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.7.tgz", + "integrity": "sha512-ZacLzja+TmJeZ1h14xW2FB/WpeimUD3haBXQPyJqxvo8jQTmfeA8zv58mtjN2C7EHXZDYVcVYdYmAxjkWVvKCw==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "4.1.5", - "@vitest/utils": "4.1.5", + "@vitest/pretty-format": "4.1.7", + "@vitest/utils": "4.1.7", "magic-string": "^0.30.21", "pathe": "^2.0.3" }, @@ -1597,9 +1700,9 @@ } }, "node_modules/@vitest/spy": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.5.tgz", - "integrity": "sha512-2lNOsh6+R2Idnf1TCZqSwYlKN2E/iDlD8sgU59kYVl+OMDmvldO1VDk39smRfpUNwYpNRVn3w4YfuC7KfbBnkQ==", + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.7.tgz", + "integrity": "sha512-kbkI5LMWakyuTIvs6fUJ5qdIVb1XVKsYJAT4OJ938cHMROYMSfmoQdZy0aaAnjbbc8F61vkoTqz/Az+/HiIu5Q==", "dev": true, "license": "MIT", "funding": { @@ -1607,13 +1710,13 @@ } }, "node_modules/@vitest/utils": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.5.tgz", - "integrity": "sha512-76wdkrmfXfqGjueGgnb45ITPyUi1ycZ4IHgC2bhPDUfWHklY/q3MdLOAB+TF1e6xfl8NxNY0ZYaPCFNWSsw3Ug==", + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.7.tgz", + "integrity": "sha512-T532WBu791cBxJlCl6SO+J14l81DQx6uQHm1bQbmCDY7nqlEIgkza/UFnSBNaUtSf41unldDFjdOBYEQC4b5Hw==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/pretty-format": "4.1.5", + "@vitest/pretty-format": "4.1.7", "convert-source-map": "^2.0.0", "tinyrainbow": "^3.1.0" }, @@ -1622,20 +1725,20 @@ } }, "node_modules/@vscode/test-cli": { - "version": "0.0.11", - "resolved": "https://registry.npmjs.org/@vscode/test-cli/-/test-cli-0.0.11.tgz", - "integrity": "sha512-qO332yvzFqGhBMJrp6TdwbIydiHgCtxXc2Nl6M58mbH/Z+0CyLR76Jzv4YWPEthhrARprzCRJUqzFvTHFhTj7Q==", + "version": "0.0.12", + "resolved": "https://registry.npmjs.org/@vscode/test-cli/-/test-cli-0.0.12.tgz", + "integrity": "sha512-iYN0fDg29+a2Xelle/Y56Xvv7Nc8Thzq4VwpzAF/SIE6918rDicqfsQxV6w1ttr2+SOm+10laGuY9FG2ptEKsQ==", "dev": true, "license": "MIT", "dependencies": { - "@types/mocha": "^10.0.2", - "c8": "^9.1.0", - "chokidar": "^3.5.3", - "enhanced-resolve": "^5.15.0", + "@types/mocha": "^10.0.10", + "c8": "^10.1.3", + "chokidar": "^3.6.0", + "enhanced-resolve": "^5.18.3", "glob": "^10.3.10", "minimatch": "^9.0.3", - "mocha": "^11.1.0", - "supports-color": "^9.4.0", + "mocha": "^11.7.4", + "supports-color": "^10.2.2", "yargs": "^17.7.2" }, "bin": { @@ -1811,6 +1914,18 @@ "node": ">=12" } }, + "node_modules/ast-v8-to-istanbul": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-1.0.0.tgz", + "integrity": "sha512-1fSfIwuDICFA4LKkCzRPO7F0hzFf0B7+Xqrl27ynQaa+Rh0e1Es0v6kWHPott3lU10AyAr7oKHa65OppjLn3Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.31", + "estree-walker": "^3.0.3", + "js-tokens": "^10.0.0" + } + }, "node_modules/async-function": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", @@ -1888,20 +2003,20 @@ "license": "ISC" }, "node_modules/c8": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/c8/-/c8-9.1.0.tgz", - "integrity": "sha512-mBWcT5iqNir1zIkzSPyI3NCR9EZCVI3WUD+AVO17MVWTSFNyUueXE82qTeampNtTr+ilN/5Ua3j24LgbCKjDVg==", + "version": "10.1.3", + "resolved": "https://registry.npmjs.org/c8/-/c8-10.1.3.tgz", + "integrity": "sha512-LvcyrOAaOnrrlMpW22n690PUvxiq4Uf9WMhQwNJ9vgagkL/ph1+D4uvjvDA5XCbykrc0sx+ay6pVi9YZ1GnhyA==", "dev": true, "license": "ISC", "dependencies": { - "@bcoe/v8-coverage": "^0.2.3", + "@bcoe/v8-coverage": "^1.0.1", "@istanbuljs/schema": "^0.1.3", "find-up": "^5.0.0", "foreground-child": "^3.1.1", "istanbul-lib-coverage": "^3.2.0", "istanbul-lib-report": "^3.0.1", "istanbul-reports": "^3.1.6", - "test-exclude": "^6.0.0", + "test-exclude": "^7.0.1", "v8-to-istanbul": "^9.0.0", "yargs": "^17.7.2", "yargs-parser": "^21.1.1" @@ -1910,7 +2025,15 @@ "c8": "bin/c8.js" }, "engines": { - "node": ">=14.14.0" + "node": ">=18" + }, + "peerDependencies": { + "monocart-coverage-reports": "^2" + }, + "peerDependenciesMeta": { + "monocart-coverage-reports": { + "optional": true + } } }, "node_modules/call-bind": { @@ -2961,13 +3084,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true, - "license": "ISC" - }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -3389,18 +3505,6 @@ "node": ">=0.8.19" } }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", - "dev": true, - "license": "ISC", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", @@ -3973,6 +4077,13 @@ "@pkgjs/parseargs": "^0.11.0" } }, + "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/js-yaml": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", @@ -4204,6 +4315,9 @@ "arm64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MPL-2.0", "optional": true, "os": [ @@ -4225,6 +4339,9 @@ "arm64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MPL-2.0", "optional": true, "os": [ @@ -4246,6 +4363,9 @@ "x64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MPL-2.0", "optional": true, "os": [ @@ -4267,6 +4387,9 @@ "x64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MPL-2.0", "optional": true, "os": [ @@ -4395,6 +4518,18 @@ "@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", @@ -4880,16 +5015,6 @@ ], "license": "MIT" }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "license": "ISC", - "dependencies": { - "wrappy": "1" - } - }, "node_modules/onetime": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", @@ -5130,16 +5255,6 @@ "node": ">=8" } }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -5248,9 +5363,9 @@ } }, "node_modules/postcss": { - "version": "8.5.14", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.14.tgz", - "integrity": "sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==", + "version": "8.5.15", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", + "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", "dev": true, "funding": [ { @@ -5268,7 +5383,7 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.11", + "nanoid": "^3.3.12", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, @@ -5460,14 +5575,14 @@ } }, "node_modules/rolldown": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0.tgz", - "integrity": "sha512-yD986aXDESFGS95spT1LAv0jssywP4npMEjmMHyN2/5+eE8qQJUype2AaKkRiLgBgyD0LFlubwAht7VmY8rGoA==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.1.tgz", + "integrity": "sha512-X0KQHljNnEkWNqqiz9zJrGunh1B0HgOxLXvnFpCOcadzcy5qohZ3tqMEUg00vncoRovXuK3ZqCT9KnnKzoInFQ==", "dev": true, "license": "MIT", "dependencies": { - "@oxc-project/types": "=0.129.0", - "@rolldown/pluginutils": "1.0.0" + "@oxc-project/types": "=0.130.0", + "@rolldown/pluginutils": "^1.0.0" }, "bin": { "rolldown": "bin/cli.mjs" @@ -5476,21 +5591,21 @@ "node": "^20.19.0 || >=22.12.0" }, "optionalDependencies": { - "@rolldown/binding-android-arm64": "1.0.0", - "@rolldown/binding-darwin-arm64": "1.0.0", - "@rolldown/binding-darwin-x64": "1.0.0", - "@rolldown/binding-freebsd-x64": "1.0.0", - "@rolldown/binding-linux-arm-gnueabihf": "1.0.0", - "@rolldown/binding-linux-arm64-gnu": "1.0.0", - "@rolldown/binding-linux-arm64-musl": "1.0.0", - "@rolldown/binding-linux-ppc64-gnu": "1.0.0", - "@rolldown/binding-linux-s390x-gnu": "1.0.0", - "@rolldown/binding-linux-x64-gnu": "1.0.0", - "@rolldown/binding-linux-x64-musl": "1.0.0", - "@rolldown/binding-openharmony-arm64": "1.0.0", - "@rolldown/binding-wasm32-wasi": "1.0.0", - "@rolldown/binding-win32-arm64-msvc": "1.0.0", - "@rolldown/binding-win32-x64-msvc": "1.0.0" + "@rolldown/binding-android-arm64": "1.0.1", + "@rolldown/binding-darwin-arm64": "1.0.1", + "@rolldown/binding-darwin-x64": "1.0.1", + "@rolldown/binding-freebsd-x64": "1.0.1", + "@rolldown/binding-linux-arm-gnueabihf": "1.0.1", + "@rolldown/binding-linux-arm64-gnu": "1.0.1", + "@rolldown/binding-linux-arm64-musl": "1.0.1", + "@rolldown/binding-linux-ppc64-gnu": "1.0.1", + "@rolldown/binding-linux-s390x-gnu": "1.0.1", + "@rolldown/binding-linux-x64-gnu": "1.0.1", + "@rolldown/binding-linux-x64-musl": "1.0.1", + "@rolldown/binding-openharmony-arm64": "1.0.1", + "@rolldown/binding-wasm32-wasi": "1.0.1", + "@rolldown/binding-win32-arm64-msvc": "1.0.1", + "@rolldown/binding-win32-x64-msvc": "1.0.1" } }, "node_modules/safe-array-concat": { @@ -6100,13 +6215,13 @@ } }, "node_modules/supports-color": { - "version": "9.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-9.4.0.tgz", - "integrity": "sha512-VL+lNrEoIXww1coLPOmiEmK/0sGigko5COxI09KzHc2VJXJsQ37UaQ+8quuxjDeA7+KnLGTWRyOXSLLR2Wb4jw==", + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-10.2.2.tgz", + "integrity": "sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==", "dev": true, "license": "MIT", "engines": { - "node": ">=12" + "node": ">=18" }, "funding": { "url": "https://github.com/chalk/supports-color?sponsor=1" @@ -6140,64 +6255,57 @@ } }, "node_modules/test-exclude": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", - "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", + "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": "^7.1.4", - "minimatch": "^3.0.4" + "glob": "^10.4.1", + "minimatch": "^10.2.2" }, "engines": { - "node": ">=8" + "node": ">=18" } }, - "node_modules/test-exclude/node_modules/brace-expansion": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz", - "integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==", + "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", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "engines": { + "node": "18 || 20 || >=22" } }, - "node_modules/test-exclude/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "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", + "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": "ISC", + "license": "MIT", "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "balanced-match": "^4.0.2" }, "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": "18 || 20 || >=22" } }, "node_modules/test-exclude/node_modules/minimatch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "dependencies": { - "brace-expansion": "^1.1.7" + "brace-expansion": "^5.0.5" }, "engines": { - "node": "*" + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/tinybench": { @@ -6508,16 +6616,16 @@ } }, "node_modules/vite": { - "version": "8.0.12", - "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.12.tgz", - "integrity": "sha512-w2dDofOWv2QB09ZITZBsvKTVAlYvPR4IAmrY/v0ir9KvLs0xybR7i48wxhM1/oyBWO34wPns+bPGw5ZrZqDpZg==", + "version": "8.0.13", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.13.tgz", + "integrity": "sha512-MFtjBYgzmSxmgA4RAfjIyXWpGe1oALnjgUTzzV7QLx/TKxCzjtMH6Fd9/eVK+5Fg1qNoz5VAwsmMs/NofrmJvw==", "dev": true, "license": "MIT", "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.4", "postcss": "^8.5.14", - "rolldown": "1.0.0", + "rolldown": "1.0.1", "tinyglobby": "^0.2.16" }, "bin": { @@ -6599,19 +6707,19 @@ } }, "node_modules/vitest": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.5.tgz", - "integrity": "sha512-9Xx1v3/ih3m9hN+SbfkUyy0JAs72ap3r7joc87XL6jwF0jGg6mFBvQ1SrwaX+h8BlkX6Hz9shdd1uo6AF+ZGpg==", + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.7.tgz", + "integrity": "sha512-flYyaFd2CgoCoU+0UKt3pxksgC+S02iTDN0n3LtqaMeXsI9SBcdNujc2k0DeFLzUn/0k538yNjOSdwgCqcrwJA==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/expect": "4.1.5", - "@vitest/mocker": "4.1.5", - "@vitest/pretty-format": "4.1.5", - "@vitest/runner": "4.1.5", - "@vitest/snapshot": "4.1.5", - "@vitest/spy": "4.1.5", - "@vitest/utils": "4.1.5", + "@vitest/expect": "4.1.7", + "@vitest/mocker": "4.1.7", + "@vitest/pretty-format": "4.1.7", + "@vitest/runner": "4.1.7", + "@vitest/snapshot": "4.1.7", + "@vitest/spy": "4.1.7", + "@vitest/utils": "4.1.7", "es-module-lexer": "^2.0.0", "expect-type": "^1.3.0", "magic-string": "^0.30.21", @@ -6639,12 +6747,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.5", - "@vitest/browser-preview": "4.1.5", - "@vitest/browser-webdriverio": "4.1.5", - "@vitest/coverage-istanbul": "4.1.5", - "@vitest/coverage-v8": "4.1.5", - "@vitest/ui": "4.1.5", + "@vitest/browser-playwright": "4.1.7", + "@vitest/browser-preview": "4.1.7", + "@vitest/browser-webdriverio": "4.1.7", + "@vitest/coverage-istanbul": "4.1.7", + "@vitest/coverage-v8": "4.1.7", + "@vitest/ui": "4.1.7", "happy-dom": "*", "jsdom": "*", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" @@ -6942,13 +7050,6 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true, - "license": "ISC" - }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", diff --git a/package.json b/package.json index 8cd7509..80b992b 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "gops", "displayName": "Gops - Visual Git Toolkit", "description": "Visual Git Toolkit for VS Code", - "version": "0.0.4", + "version": "0.0.1", "publisher": "codemanxdev", "repository": { "type": "git", @@ -155,26 +155,31 @@ } }, "scripts": { + "vsce:package": "vsce package --no-dependencies", "vscode:prepublish": "npm run package", - "compile": "npm run check-types && npm run lint && node esbuild.js", + "compile": "npm run check:types && npm run lint && node esbuild.js", "watch": "npm-run-all -p watch:*", "watch:esbuild": "node esbuild.js --watch", "watch:tsc": "tsc --noEmit --watch --project tsconfig.json", - "package": "npm run check-types && npm run lint && node esbuild.js --production", + "package": "npm run check:types && npm run lint && node esbuild.js --production", "build": "npm run package", - "compile-tests": "tsc -p . --outDir out", - "watch-tests": "tsc -p . -w --outDir out", - "pretest": "npm run compile-tests && npm run compile && npm run lint", - "check-types": "tsc --noEmit", + "compile:tests": "tsc -p test/tsconfig.json --outDir out", + "watch:tests": "tsc -p test/tsconfig.json -w --outDir out", + "check:test:types": "tsc -p test/tsconfig.json --noEmit", + "pretest": "npm run compile:tests && npm run compile && npm run lint", + "check:types": "tsc --noEmit", "lint": "eslint src", - "test": "vscode-test", - "test:unit": "vitest" + "test:unit": "vitest run", + "test:integration": "vscode-test", + "coverage:unit": "vitest run --coverage --coverage.reporter=lcov --coverage.reportsDirectory=coverage/unit", + "coverage:integration": "vscode-test --coverage --coverage-reporter lcov --coverage-output ./coverage/integration" }, "devDependencies": { "@types/mocha": "^10.0.10", "@types/node": "^22.19.19", "@types/vscode": "^1.110.0", - "@vscode/test-cli": "^0.0.11", + "@vitest/coverage-v8": "^4.1.7", + "@vscode/test-cli": "^0.0.12", "@vscode/test-electron": "^2.5.2", "esbuild": "^0.27.3", "eslint": "^9.39.3", @@ -186,5 +191,17 @@ }, "dependencies": { "simple-git": "^3.36.0" + }, + "c8": { + "reporter": [ + "lcov", + "text" + ], + "include": [ + "src/**/*.ts" + ], + "exclude": [ + "**/*.test.ts" + ] } } diff --git a/test/extension.test.ts b/test/extension.test.ts index 6314ea1..f81d1bb 100644 --- a/test/extension.test.ts +++ b/test/extension.test.ts @@ -1,18 +1,18 @@ -import * as assert from 'assert'; +import * as assert from "assert"; // You can import and use all API from the 'vscode' module // as well as import your extension to test it -import * as vscode from 'vscode'; +import * as vscode from "vscode"; // import * as myExtension from '../../extension'; declare function suite(name: string, fn: () => void): void; declare function test(name: string, fn: () => void): void; -suite('Extension Test Suite', () => { - vscode.window.showInformationMessage('Start all tests.'); +suite("Extension Test Suite", () => { + vscode.window.showInformationMessage("Start all tests."); - test('Sample test', () => { - assert.strictEqual(-1, [1, 2, 3].indexOf(5)); - assert.strictEqual(-1, [1, 2, 3].indexOf(0)); - }); + test("Sample test", () => { + assert.strictEqual(-1, [1, 2, 3].indexOf(5)); + assert.strictEqual(-1, [1, 2, 3].indexOf(0)); + }); }); diff --git a/test/integration/extensionIntegration.test.ts b/test/integration/extensionIntegration.test.ts new file mode 100644 index 0000000..df2b2c1 --- /dev/null +++ b/test/integration/extensionIntegration.test.ts @@ -0,0 +1,103 @@ +import * as assert from "node:assert"; +import * as vscode from "vscode"; + +/** + * Integration tests for the Gops extension. + * + * The workspace folder is opened by `.vscode-test.mjs` via the `workspaceFolder` + * config field. The folder is a minimal git repo created on-the-fly by the config + * script, so GitService / simple-git have a valid repo to work against. + */ +suite("Extension Integration Test Suite", function () { + this.timeout(30000); + + test("Extension should be activated and execute commands", async function () { + // The workspace is opened by the test runner — confirm it is set + assert.ok( + vscode.workspace.workspaceFolders?.length, + "Workspace folder from workspaceFolder config must be available", + ); + + const extension = vscode.extensions.getExtension("codemanxdev.gops"); + assert.ok(extension, "Extension codemanxdev.gops must be found by VS Code"); + + if (!extension.isActive) { + await extension.activate(); + } + + assert.strictEqual( + extension.isActive, + true, + "Extension must be active after activation", + ); + + // gops.refresh is a no-args command registered by CommandRegistrar in activate(). + // A clean resolve confirms the command registry is intact and tree view is registered. + await vscode.commands.executeCommand("gops.refresh"); + assert.ok(true, "gops.refresh must complete without error"); + }); + + test("Extension should register the Git Ops tree view", async function () { + assert.ok( + vscode.workspace.workspaceFolders?.length, + "Workspace folder from workspaceFolder config must be available", + ); + + const extension = vscode.extensions.getExtension("codemanxdev.gops"); + assert.ok(extension, "Extension codemanxdev.gops must be found by VS Code"); + + if (!extension.isActive) { + await extension.activate(); + } + + // Allow tree data provider to populate once + await new Promise((resolve) => setTimeout(resolve, 500)); + + assert.strictEqual( + extension.isActive, + true, + "Extension must be active before checking tree view registration", + ); + + // extension.ts activate() calls: + // vscode.window.createTreeView('gitOpsTreeview', { treeDataProvider }) + // Calling createTreeView again with the same id throws VS Code error E303 + // ('Object for type "view" already exists'). Catching that error proves the + // extension registered the tree view in activate(). + let treeViewAlreadyRegistered = false; + try { + vscode.window.createTreeView("gitOpsTreeview", { + treeDataProvider: + new (class implements vscode.TreeDataProvider { + getTreeItem( + _element: unknown, + ): vscode.TreeItem | Thenable { + return undefined as unknown as vscode.TreeItem; + } + getChildren(): vscode.TreeItem[] | Thenable { + return []; + } + })(), + }); + // If we reach here the view was NOT registered — fail + assert.fail( + 'createTreeView should have thrown "already exists" since activate() registered the view first', + ); + } catch (err: any) { + if ( + typeof err.message === "string" && + err.message.includes("already exists") + ) { + treeViewAlreadyRegistered = true; + } else { + throw err; + } + } + + assert.ok( + treeViewAlreadyRegistered, + 'createTreeView("gitOpsTreeview") must throw "already exists" — ' + + "proving the extension registered the Git Ops tree view in activate()", + ); + }); +}); diff --git a/test/tsconfig.json b/test/tsconfig.json new file mode 100644 index 0000000..0bd06cd --- /dev/null +++ b/test/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "module": "Node16", + "target": "ES2022", + "moduleResolution": "Node16", + "types": ["node", "mocha"], + "strict": true, + "rootDir": ".." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "../dist", "unit/**/*.ts"] +} diff --git a/test/types.d.ts b/test/types.d.ts new file mode 100644 index 0000000..8d7b091 --- /dev/null +++ b/test/types.d.ts @@ -0,0 +1,4 @@ +/// +/// + +export {}; diff --git a/test/unit/logging/Logger.test.ts b/test/unit/logging/Logger.test.ts new file mode 100644 index 0000000..8a215f9 --- /dev/null +++ b/test/unit/logging/Logger.test.ts @@ -0,0 +1,47 @@ +/// +import { describe, it, expect, beforeEach, vi } from "vitest"; +import { Logger } from "../../../src/logging/Logger"; + +vi.mock("vscode", () => ({ + window: { + createOutputChannel: vi.fn(() => ({ + appendLine: vi.fn(), + show: vi.fn(), + dispose: vi.fn(), + })), + }, +})); + +describe("Logger", () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it("writes an info message to the output channel", () => { + const logChannel = (Logger as any).channel; + const appendSpy = vi.spyOn(logChannel, "appendLine"); + + Logger.info("test info"); + + expect(appendSpy).toHaveBeenCalledWith("[INFO] test info"); + }); + + it("writes a warning and error message to the output channel", () => { + const logChannel = (Logger as any).channel; + const appendSpy = vi.spyOn(logChannel, "appendLine"); + + Logger.warn("test warn"); + Logger.error("test error"); + Logger.debug("test debug"); + + expect(appendSpy).toHaveBeenCalledWith("[WARN] test warn"); + expect(appendSpy).toHaveBeenCalledWith("[ERROR] test error"); + expect(appendSpy).toHaveBeenCalledWith("[DEBUG] test debug"); + }); + + it("parses errors, strings, and unknown values", () => { + expect(Logger.getErrorMessage(new Error("boom"))).toBe("boom"); + expect(Logger.getErrorMessage("boom")).toBe("boom"); + expect(Logger.getErrorMessage({})).toBe("An unknown error occurred."); + }); +}); diff --git a/test/unit/notifications/Notifications.test.ts b/test/unit/notifications/Notifications.test.ts new file mode 100644 index 0000000..729e8ef --- /dev/null +++ b/test/unit/notifications/Notifications.test.ts @@ -0,0 +1,52 @@ +/// +import { describe, it, expect, beforeEach, vi } from "vitest"; +import { Notifications } from "../../../src/notifications/Notifications"; +import { Logger } from "../../../src/logging/Logger"; + +vi.mock("vscode", () => ({ + window: { + createOutputChannel: vi.fn(() => ({ + appendLine: vi.fn(), + show: vi.fn(), + dispose: vi.fn(), + })), + showInformationMessage: vi.fn().mockResolvedValue("ok"), + showWarningMessage: vi.fn().mockResolvedValue("warned"), + showErrorMessage: vi.fn().mockResolvedValue("Show Output"), + }, +})); + +describe("Notifications", () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it("shows info messages", async () => { + const result = await Notifications.info("info message"); + expect(result).toBe("ok"); + }); + + it("shows warning messages", async () => { + const result = await Notifications.warning("warning message"); + expect(result).toBe("warned"); + }); + + it("opens output when the user selects Show Output", async () => { + const showSpy = vi.spyOn(Logger, "show"); + + await Notifications.errorWithOutput("error occurred"); + + expect(showSpy).toHaveBeenCalled(); + }); + + it("does not open output when the user dismisses the error notification", async () => { + const vscodeMock = await import("vscode"); + (vscodeMock.window.showErrorMessage as unknown as ReturnType).mockResolvedValue(undefined); + + const showSpy = vi.spyOn(Logger, "show"); + + await Notifications.errorWithOutput("error occurred"); + + expect(showSpy).not.toHaveBeenCalled(); + }); +}); diff --git a/test/unit/services/DiffService.test.ts b/test/unit/services/DiffService.test.ts new file mode 100644 index 0000000..d721821 --- /dev/null +++ b/test/unit/services/DiffService.test.ts @@ -0,0 +1,58 @@ +/// +import { vi, describe, it, expect, beforeEach, afterEach } from "vitest"; +import * as vscode from "vscode"; +import { DiffService } from "../../../src/services/DiffService"; +import { FileService } from "../../../src/services/FileService"; +import { GitService } from "../../../src/services/GitService"; +import { DiffRequest } from "../../../src/models/DiffRequest"; + +vi.mock("vscode", () => ({ + commands: { + executeCommand: vi.fn(), + }, + Uri: { + file: (value: string) => ({ fsPath: value }), + }, +})); + +describe("DiffService", () => { + let fileService: FileService; + let gitService: GitService; + let diffService: DiffService; + + beforeEach(() => { + fileService = { createTempFile: vi.fn() } as unknown as FileService; + gitService = { getFileContent: vi.fn() } as unknown as GitService; + diffService = new DiffService(fileService, gitService); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + it("opens a diff with temporary left content and workspace right file", async () => { + const request: DiffRequest = { + left: { repositoryPath: "/repo", fileName: "left.txt" }, + right: { repositoryPath: "/repo", fileName: "right.txt" }, + title: "My Diff", + }; + + (gitService.getFileContent as unknown as ReturnType).mockResolvedValue( + "left content", + ); + (fileService.createTempFile as unknown as ReturnType).mockResolvedValue( + "/tmp/left.txt", + ); + + await diffService.openDiff(request); + + expect(gitService.getFileContent).toHaveBeenCalledWith("HEAD", "left.txt"); + expect(fileService.createTempFile).toHaveBeenCalledWith("left.txt", "left content"); + expect(vscode.commands.executeCommand).toHaveBeenCalledWith( + "vscode.diff", + { fsPath: "/tmp/left.txt" }, + { fsPath: "/repo/right.txt" }, + "My Diff", + ); + }); +}); diff --git a/test/unit/services/FileService.test.ts b/test/unit/services/FileService.test.ts new file mode 100644 index 0000000..9f30782 --- /dev/null +++ b/test/unit/services/FileService.test.ts @@ -0,0 +1,49 @@ +/// +/// +import { describe, it, expect, beforeEach, afterEach } from "vitest"; +import * as fs from "fs"; +import * as os from "os"; +import * as path from "path"; +import { FileService } from "../../../src/services/FileService"; + +describe("FileService", () => { + let storagePath: string; + let fileService: FileService; + + beforeEach(async () => { + storagePath = await fs.promises.mkdtemp( + path.join(os.tmpdir(), "gops-file-service-"), + ); + fileService = new FileService(storagePath); + }); + + afterEach(async () => { + await fs.promises.rm(storagePath, { recursive: true, force: true }); + }); + + it("creates a temp file under the storage temp directory", async () => { + const fileName = "path/to/example.txt"; + const content = "hello world"; + + const tempFilePath = await fileService.createTempFile(fileName, content); + + expect(tempFilePath).toContain(path.join(storagePath, "temp")); + expect(tempFilePath).toContain("example.txt"); + + const written = await fs.promises.readFile(tempFilePath, "utf8"); + expect(written).toBe(content); + }); + + it("creates a temp file from a path-like file name", async () => { + const fileName = "nested\\path/example.txt"; + const content = "nested content"; + + const tempFilePath = await fileService.createTempFile(fileName, content); + + expect(tempFilePath).toContain(path.join(storagePath, "temp")); + expect(tempFilePath).toContain("nested_path_example.txt"); + + const written = await fs.promises.readFile(tempFilePath, "utf8"); + expect(written).toBe(content); + }); +}); diff --git a/test/unit/services/GitService.test.ts b/test/unit/services/GitService.test.ts new file mode 100644 index 0000000..ac5abb5 --- /dev/null +++ b/test/unit/services/GitService.test.ts @@ -0,0 +1,239 @@ +/// +import { vi, describe, it, expect, beforeEach, afterEach } from "vitest"; +import { GitService } from "../../../src/services/GitService"; +import { Logger } from "../../../src/logging/Logger"; +import { Notifications } from "../../../src/notifications/Notifications"; + +vi.mock("vscode", () => ({ + workspace: { + workspaceFolders: [ + { + uri: { + fsPath: "/workspace/gops", + }, + }, + ], + }, + window: { + createOutputChannel: vi.fn(() => ({ appendLine: vi.fn(), show: vi.fn(), dispose: vi.fn() })), + showInformationMessage: vi.fn(), + showWarningMessage: vi.fn(), + showErrorMessage: vi.fn(), + }, + Uri: { + file: (value: string) => ({ fsPath: value }), + }, + commands: { + executeCommand: vi.fn(), + }, +})); + +const mockGit = { + status: vi.fn(), + branch: vi.fn(), + branchLocal: vi.fn(), + getRemotes: vi.fn(), + tags: vi.fn(), + stashList: vi.fn(), + log: vi.fn(), + show: vi.fn(), + checkout: vi.fn(), + checkoutBranch: vi.fn(), + push: vi.fn(), + commit: vi.fn(), + pull: vi.fn(), + checkoutLocalBranch: vi.fn(), +}; + +vi.mock("simple-git", () => ({ + __esModule: true, + default: vi.fn(() => mockGit), +})); + +describe("GitService", () => { + let service: GitService; + + beforeEach(() => { + Object.values(mockGit).forEach((fn) => fn.mockReset()); + vi.restoreAllMocks(); + service = new GitService("/workspace/gops"); + }); + + it("parses ahead/behind values for local branches", async () => { + mockGit.branchLocal.mockResolvedValue({ + all: ["main", "feature"], + current: "main", + branches: { + main: { label: "behind 3" }, + feature: { label: "ahead 2" }, + }, + }); + + const branches = await service.getLocalBranches(); + + expect(branches).toEqual([ + { name: "main", current: true, ahead: 0, behind: 3 }, + { name: "feature", current: false, ahead: 2, behind: 0 }, + ]); + }); + + it("filters remote branches by remote prefix", async () => { + mockGit.branch.mockResolvedValue({ + all: ["origin/main", "origin/feature", "other/ignore"], + }); + + const branches = await service.getRemoteBranches("origin"); + + expect(branches).toEqual([ + { name: "main", remote: "origin" }, + { name: "feature", remote: "origin" }, + ]); + }); + + it("returns repository name and path from workspace folder", () => { + expect(service.getRepoName()).toBe("gops"); + expect(service.getRepoPath()).toBe("/workspace/gops"); + }); + + it("logs and notifies on successful checkout", async () => { + mockGit.checkout.mockResolvedValue("ok"); + const infoSpy = vi.spyOn(Logger, "info"); + const notifySpy = vi.spyOn(Notifications, "info"); + + const result = await service.checkout("feature"); + + expect(result).toBe("ok"); + expect(infoSpy).toHaveBeenCalledWith("Checked out branch feature successfully"); + expect(notifySpy).toHaveBeenCalledWith("Checked out branch feature successfully"); + }); + + it("returns git status from the repository", async () => { + const statusResult = { isClean: true } as const; + mockGit.status.mockResolvedValue(statusResult); + + expect(await service.getStatus()).toBe(statusResult); + expect(mockGit.status).toHaveBeenCalled(); + }); + + it("returns branch summary from branch call", async () => { + const branchSummary = { current: "main", all: ["main"] } as const; + mockGit.branch.mockResolvedValue(branchSummary); + + expect(await service.getBranches()).toBe(branchSummary); + expect(mockGit.branch).toHaveBeenCalled(); + }); + + it("returns configured remotes", async () => { + const remotes = [{ name: "origin", refs: {} }]; + mockGit.getRemotes.mockResolvedValue(remotes as any); + + expect(await service.getRemotes()).toBe(remotes); + }); + + it("returns all tags", async () => { + mockGit.tags.mockResolvedValue({ all: ["v1.0.0", "v1.1.0"] }); + + expect(await service.getTags()).toEqual(["v1.0.0", "v1.1.0"]); + }); + + it("returns stash messages from stash list", async () => { + mockGit.stashList.mockResolvedValue({ all: [{ message: "WIP" }] } as any); + + expect(await service.getStash()).toEqual(["WIP"]); + }); + + it("returns commit log entries", async () => { + const logEntries = [{ hash: "abc" }] as any; + mockGit.log.mockResolvedValue({ all: logEntries }); + + expect(await service.getLog()).toBe(logEntries); + }); + + it("returns file content from a git ref", async () => { + mockGit.show.mockResolvedValue("file contents"); + + expect(await service.getFileContent("HEAD", "README.md")).toBe("file contents"); + expect(mockGit.show).toHaveBeenCalledWith(["HEAD:README.md"]); + }); + + it("returns the current branch name", async () => { + mockGit.branch.mockResolvedValue({ current: "develop" }); + + expect(await service.getCurrentBranch()).toBe("develop"); + }); + + it("logs and notifies on successful push", async () => { + mockGit.push.mockResolvedValue("ok"); + const infoSpy = vi.spyOn(Logger, "info"); + const notifySpy = vi.spyOn(Notifications, "info"); + + const result = await service.push(); + + expect(result).toBe("ok"); + expect(infoSpy).toHaveBeenCalledWith("Pushed to remote successfully"); + expect(notifySpy).toHaveBeenCalledWith("Pushed to remote successfully"); + }); + + it("logs and notifies on successful commit", async () => { + mockGit.commit.mockResolvedValue("commit-ok"); + const infoSpy = vi.spyOn(Logger, "info"); + const notifySpy = vi.spyOn(Notifications, "info"); + + const result = await service.commit("message"); + + expect(result).toBe("commit-ok"); + expect(infoSpy).toHaveBeenCalledWith("Commit successful"); + expect(notifySpy).toHaveBeenCalledWith("Commit successful"); + }); + + it("logs and notifies on successful pull", async () => { + mockGit.pull.mockResolvedValue("ok"); + const infoSpy = vi.spyOn(Logger, "info"); + const notifySpy = vi.spyOn(Notifications, "info"); + + const result = await service.pull(); + + expect(result).toBe("ok"); + expect(infoSpy).toHaveBeenCalledWith("Pull successful"); + expect(notifySpy).toHaveBeenCalledWith("Pull successful"); + }); + + it("logs and notifies on successful checkoutBranch", async () => { + mockGit.checkoutBranch.mockResolvedValue("ok"); + const infoSpy = vi.spyOn(Logger, "info"); + const notifySpy = vi.spyOn(Notifications, "info"); + + const result = await service.checkoutBranch("feature","main"); + + expect(result).toBe("ok"); + expect(infoSpy).toHaveBeenCalledWith("Checked out branch feature successfully"); + expect(notifySpy).toHaveBeenCalledWith("Checked out branch feature successfully"); + }); + + it("logs and notifies on successful checkoutLocalBranch", async () => { + mockGit.checkoutLocalBranch.mockResolvedValue("ok"); + const infoSpy = vi.spyOn(Logger, "info"); + const notifySpy = vi.spyOn(Notifications, "info"); + + const result = await service.checkoutLocalBranch("feature"); + + expect(result).toBe("ok"); + expect(infoSpy).toHaveBeenCalledWith("Branch feature created successfully"); + expect(notifySpy).toHaveBeenCalledWith("Branch feature created successfully"); + }); + + it("logs error and rethrows when checkout fails", async () => { + const error = new Error("checkout failed"); + mockGit.checkout.mockRejectedValue(error); + const errorSpy = vi.spyOn(Logger, "error"); + const notifySpy = vi.spyOn(Notifications, "errorWithOutput"); + + await expect(service.checkout("feature")).rejects.toThrow(error); + expect(errorSpy).toHaveBeenCalledWith( + "Checkout failed for branch feature: checkout failed", + ); + expect(notifySpy).toHaveBeenCalledWith( + "Checkout failed for branch feature. See details in output", + ); + }); +}); diff --git a/tsconfig.json b/tsconfig.json index 7ecbdf5..e407c4e 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -13,5 +13,11 @@ // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ // "noUnusedParameters": true, /* Report errors on unused parameters. */ }, - "exclude": ["vitest.config.mts", "node_modules", "dist", "test", "**/*.test.ts"] + "exclude": [ + "vitest.config.mts", + "node_modules", + "dist", + "test", + "**/*.test.ts" + ] }