diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml new file mode 100644 index 000000000..99a941714 --- /dev/null +++ b/.github/workflows/coverage.yml @@ -0,0 +1,90 @@ +# Run vitest+v8 coverage on PRs and main pushes. +# +# Uploads the coverage report as an artifact for human download and +# writes a summary table to the workflow run page. Currently does NOT +# gate on threshold and does NOT post deltas to PRs — coverage is +# informational. Closed-loop dark-factory feedback (PR comment + delta +# gate) is tracked as a follow-up issue. +# +# Runs against the chromium browser project only. Firefox/WebKit +# coverage is not measurable (JSC and SpiderMonkey don't expose V8's +# coverage protocol). See vitest.config.ts coverage block for details. +name: Coverage + +on: + pull_request: + push: + branches: [main] + +# One coverage run per ref; cancel in-progress runs when a new push +# arrives so we always report on the current tip. +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + coverage: + name: vitest+v8 coverage + runs-on: ubuntu-latest + container: + image: mcr.microsoft.com/playwright:v1.59.1-noble + + steps: + - name: Checkout code + uses: actions/checkout@v6 + + - name: Install Bun + run: python3 tools/install-bun + + # The Playwright noble image ships without jq; the coverage-summary + # step needs it to format the workflow run summary table. + - name: Install jq + run: apt-get update && apt-get install -y --no-install-recommends jq + + - name: Install dependencies + run: bun install --frozen-lockfile + + - name: Run coverage + run: bun run test:coverage + env: + HOME: /root + + - name: Coverage summary + if: always() + run: | + if [ -f coverage/coverage-summary.json ]; then + echo "## Coverage summary" >> "$GITHUB_STEP_SUMMARY" + echo "" >> "$GITHUB_STEP_SUMMARY" + echo "| Metric | Pct | Covered/Total |" >> "$GITHUB_STEP_SUMMARY" + echo "|---|---|---|" >> "$GITHUB_STEP_SUMMARY" + jq -r ' + .total | + "| Lines | \(.lines.pct)% | \(.lines.covered)/\(.lines.total) |\n" + + "| Statements | \(.statements.pct)% | \(.statements.covered)/\(.statements.total) |\n" + + "| Branches | \(.branches.pct)% | \(.branches.covered)/\(.branches.total) |\n" + + "| Functions | \(.functions.pct)% | \(.functions.covered)/\(.functions.total) |" + ' coverage/coverage-summary.json >> "$GITHUB_STEP_SUMMARY" + fi + + # Machine-readable artifacts (~220 KB), retained 30 days. Designed + # so a future closed-loop coverage workflow can download a base + # commit's data to compute deltas. + - name: Upload coverage data + if: always() + uses: actions/upload-artifact@v7 + with: + name: coverage-data + path: | + coverage/coverage-summary.json + coverage/lcov.info + retention-days: 30 + + # Human-readable HTML report (~5 MB). Only on main pushes; short + # retention since PR runs don't benefit from it. + - name: Upload coverage HTML (main only) + if: always() && github.event_name == 'push' && github.ref == 'refs/heads/main' + uses: actions/upload-artifact@v7 + with: + name: coverage-html + path: coverage/lcov-report + retention-days: 7 diff --git a/.gitignore b/.gitignore index be48bc761..bd410d818 100644 --- a/.gitignore +++ b/.gitignore @@ -22,6 +22,7 @@ node_modules _repo coverage coverage-temp +COVERAGE.md builds/**/meta .nyc_output **/dist/* diff --git a/COVERAGE.md b/COVERAGE.md deleted file mode 100644 index 9530ef437..000000000 --- a/COVERAGE.md +++ /dev/null @@ -1,176 +0,0 @@ -# Coverage-Report -Date: 2026-01-10 - -File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s ---------------------------------------|---------|----------|---------|---------|-------------------- -All files | 89.73 | 83.31 | 86.25 | 89.77 | - builds/reference/src | 71.42 | 100 | 0 | 100 | - index.ts | 71.42 | 100 | 0 | 100 | - packages/bind/dist | 96.37 | 90.78 | 96.55 | 96.27 | - BindingHandler.js | 93.33 | 75 | 84.61 | 93.1 | 32,47 - BindingResult.js | 100 | 100 | 100 | 100 | - DescendantBindingHandler.js | 100 | 100 | 100 | 100 | - LegacyBindingHandler.js | 95.12 | 88.88 | 92.3 | 95.12 | 9,89 - applyBindings.js | 94.11 | 87.5 | 100 | 93.95 | ...316,319,336-343 - arrayToDomNodeChildren.js | 99.08 | 97.5 | 100 | 99.01 | 51 - bindingContext.js | 98.52 | 93.18 | 100 | 98.52 | 26 - bindingEvent.js | 100 | 100 | 100 | 100 | - packages/bind/src | 26.74 | 2.27 | 23.52 | 26.74 | - BindingHandler.ts | 60 | 0 | 44.44 | 60 | 67-71,80,93-101 - LegacyBindingHandler.ts | 100 | 50 | 100 | 100 | 12 - arrayToDomNodeChildren.ts | 100 | 100 | 100 | 100 | - bindingContext.ts | 8.33 | 0 | 0 | 8.33 | 75-158,166-231 - bindingEvent.ts | 100 | 100 | 100 | 100 | - packages/binding.component/dist | 97.93 | 92.85 | 100 | 97.84 | - componentBinding.js | 97.1 | 93.33 | 100 | 97.01 | 19,120 - index.js | 100 | 100 | 100 | 100 | - slotBinding.js | 100 | 91.66 | 100 | 100 | 11 - packages/binding.core/dist | 93.17 | 87.19 | 88.09 | 93.73 | - attr.js | 94.73 | 87.5 | 100 | 94.73 | 15 - checked.js | 98.33 | 97.77 | 100 | 98.11 | 72 - click.js | 100 | 100 | 100 | 100 | - css.js | 100 | 100 | 100 | 100 | - descendantsComplete.js | 50 | 0 | 50 | 50 | 6-7 - enableDisable.js | 100 | 100 | 100 | 100 | - event.js | 100 | 100 | 100 | 100 | - hasfocus.js | 93.75 | 92.85 | 100 | 93.75 | 17,44 - html.js | 100 | 100 | 100 | 100 | - index.js | 100 | 100 | 100 | 100 | - let.js | 100 | 100 | 100 | 100 | - options.js | 97.4 | 93.54 | 100 | 97.14 | 42,140 - selectedOptions.js | 100 | 100 | 100 | 100 | - style.js | 93.75 | 83.33 | 100 | 93.33 | 14 - submit.js | 83.33 | 50 | 100 | 83.33 | 7,19 - text.js | 100 | 100 | 100 | 100 | - textInput.js | 79.03 | 58.69 | 53.84 | 80.32 | ...0,85,90,108-112 - uniqueName.js | 100 | 100 | 100 | 100 | - using.js | 100 | 100 | 100 | 100 | - value.js | 87.93 | 86.84 | 80 | 92.59 | 19-21,70 - visible.js | 100 | 100 | 100 | 100 | - packages/binding.foreach/dist | 92.18 | 86.08 | 93.02 | 91.59 | - foreach.js | 92.11 | 86.08 | 93.02 | 91.51 | ...290,332,404,416 - index.js | 100 | 100 | 100 | 100 | - packages/binding.if/dist | 96.73 | 92.68 | 92.59 | 96.47 | - ConditionalBindingHandler.js | 95.83 | 100 | 83.33 | 95.45 | 8,25 - else.js | 92.3 | 80 | 100 | 92.3 | 26 - ifUnless.js | 100 | 100 | 100 | 100 | - index.js | 100 | 100 | 100 | 100 | - with.js | 100 | 85.71 | 100 | 100 | 18 - packages/binding.template/dist | 93.3 | 89.52 | 91.3 | 93.2 | - foreach.js | 100 | 100 | 100 | 100 | - index.js | 100 | 100 | 100 | 100 | - nativeTemplateEngine.js | 80 | 50 | 100 | 80 | 15-16 - templateEngine.js | 75 | 66.66 | 60 | 75 | 9-12,19,27 - templateSources.js | 91.3 | 90 | 87.5 | 90.47 | 32,39-42 - templating.js | 95.97 | 91.85 | 96.66 | 95.97 | ...111,179-180,236 - packages/binding.template/helpers | 77.55 | 65.62 | 70 | 78.26 | - dummyTemplateEngine.ts | 77.55 | 65.62 | 70 | 78.26 | ...39,73,97-99,102 - packages/builder/dist | 72.22 | 50 | 33.33 | 72.22 | - Builder.js | 72.22 | 50 | 33.33 | 72.22 | 85-88,186,201-204 - packages/computed/dist | 96.53 | 96.22 | 85.48 | 96.4 | - computed.js | 99.14 | 98.62 | 97.29 | 99.1 | 244,327 - proxy.js | 78.37 | 72.72 | 57.89 | 78.37 | 47-48,58-73 - throttleExtender.js | 100 | 100 | 100 | 100 | - when.js | 100 | 66.66 | 100 | 100 | 17 - packages/filter.punches/dist | 96.42 | 100 | 87.5 | 96.15 | - index.js | 96.42 | 100 | 87.5 | 96.15 | 55 - packages/lifecycle/dist | 79.59 | 68 | 90.9 | 78.72 | - LifeCycle.js | 79.59 | 68 | 90.9 | 78.72 | ...,37,41,69-70,88 - packages/lifecycle/src | 91.66 | 68 | 90.9 | 93.47 | - LifeCycle.ts | 91.66 | 68 | 90.9 | 93.47 | 29,48,101 - packages/observable/dist | 96.39 | 92.99 | 93.04 | 96.78 | - Subscription.js | 88.23 | 100 | 66.66 | 87.5 | 25-28 - defer.js | 93.33 | 50 | 100 | 93.33 | 6 - dependencyDetection.js | 96.42 | 88.88 | 100 | 96 | 20 - extenders.js | 96.55 | 93.75 | 100 | 96.29 | 18 - index.js | 100 | 100 | 100 | 100 | - mappingHelpers.js | 100 | 100 | 100 | 100 | - observable.js | 100 | 94.87 | 100 | 100 | 67,94 - observableArray.changeTracking.js | 100 | 100 | 100 | 100 | - observableArray.js | 95.18 | 90 | 95.45 | 95 | 57,84,124-125 - subscribable.js | 90.58 | 83.72 | 80.76 | 93.42 | 107,119-124 - subscribableSymbol.js | 100 | 100 | 100 | 100 | - packages/provider.attr/dist | 68.96 | 25 | 100 | 66.66 | - AttributeProvider.js | 68.96 | 25 | 100 | 66.66 | ...,39-44,47,52-55 - packages/provider.bindingstring/dist | 96.55 | 100 | 87.5 | 96.29 | - BindingStringProvider.js | 96.29 | 100 | 85.71 | 96 | 44 - index.js | 100 | 100 | 100 | 100 | - packages/provider.bindingstring/src | 60.86 | 50 | 66.66 | 63.63 | - BindingStringProvider.ts | 60.86 | 50 | 66.66 | 63.63 | 22-26,29,50,57 - packages/provider.component/dist | 91.83 | 86.95 | 91.66 | 91.3 | - ComponentProvider.js | 91.83 | 86.95 | 91.66 | 91.3 | 24,34,46,60 - packages/provider.databind/dist | 85.71 | 100 | 100 | 84.61 | - DataBindProvider.js | 83.33 | 100 | 100 | 81.81 | 15,21 - index.js | 100 | 100 | 100 | 100 | - packages/provider.multi/dist | 100 | 92.3 | 100 | 100 | - MultiProvider.js | 100 | 92.3 | 100 | 100 | 54 - packages/provider.mustache/dist | 51.81 | 43.85 | 46.15 | 52.83 | - AttributeMustacheProvider.js | 14.63 | 28 | 15.38 | 15.78 | 17-73 - TextMustacheProvider.js | 90.47 | 77.77 | 100 | 90 | 14,23 - mustacheParser.js | 66.66 | 47.82 | 66.66 | 66.66 | ...,53-56,62-65,72 - packages/provider.mustache/src | 96.36 | 87.71 | 96.15 | 96.29 | - AttributeMustacheProvider.ts | 90.24 | 84 | 92.3 | 90 | 46-49,100 - TextMustacheProvider.ts | 100 | 100 | 100 | 100 | - mustacheParser.ts | 100 | 86.95 | 100 | 100 | 14-25,95 - packages/provider.native/dist | 100 | 91.66 | 100 | 100 | - NativeProvider.js | 100 | 91.66 | 100 | 100 | 17 - packages/provider.virtual/dist | 100 | 72.72 | 100 | 100 | - VirtualProvider.js | 100 | 72.72 | 100 | 100 | 20-24 - packages/provider/dist | 59.32 | 50 | 51.85 | 58.49 | - BindingHandlerObject.js | 84.61 | 60 | 100 | 83.33 | 10,18 - Provider.js | 48.83 | 47.82 | 43.47 | 47.36 | ...48-59,69,77-100 - index.js | 100 | 100 | 100 | 100 | - packages/provider/src | 19.14 | 32.14 | 8.33 | 20.45 | - BindingHandlerObject.ts | 10 | 0 | 0 | 10 | 5-27 - Provider.ts | 21.62 | 39.13 | 9.09 | 23.52 | 27,38-96,111-143 - packages/utils.component/dist | 95.45 | 92.4 | 97.67 | 95.3 | - ComponentABC.js | 100 | 100 | 100 | 100 | - index.js | 100 | 100 | 100 | 100 | - loaders.js | 93.25 | 90.38 | 95.65 | 93.02 | ...110,130,152,160 - registry.js | 98 | 96 | 100 | 97.91 | 62 - packages/utils.functionrewrite/dist | 88.88 | 75 | 100 | 88.88 | - functionRewrite.js | 88.88 | 75 | 100 | 88.88 | 11 - packages/utils.jsx/dist | 82.12 | 72.95 | 74.41 | 84.26 | - JsxObserver.js | 83.24 | 74.07 | 75.67 | 85.79 | ...287,303,351-352 - jsx.js | 86.66 | 54.54 | 66.66 | 86.66 | 11,28 - jsxClean.js | 61.53 | 100 | 66.66 | 61.53 | 18-23 - packages/utils.jsx/src | 96.35 | 89.18 | 92.5 | 97.26 | - JsxObserver.ts | 96.08 | 88.88 | 91.89 | 97.05 | ...133,150,220,286 - jsxClean.ts | 100 | 100 | 100 | 100 | - packages/utils.parser/dist | 92.42 | 88.35 | 89.23 | 92.4 | - Arguments.js | 100 | 100 | 100 | 100 | - Expression.js | 90 | 0 | 100 | 88.88 | 14 - Identifier.js | 93.1 | 86.04 | 100 | 92.98 | 93-96,115 - Node.js | 96.29 | 91.3 | 100 | 96.07 | 17,91 - Parameters.js | 94.11 | 90 | 85.71 | 93.54 | 21,31 - Parser.js | 91.5 | 87.81 | 95.91 | 91.62 | ...636,668,679,723 - Ternary.js | 100 | 100 | 100 | 100 | - identifierExpressions.js | 100 | 100 | 100 | 100 | - index.js | 90.9 | 0 | 100 | 90.9 | 13 - operators.js | 87.17 | 75 | 71.05 | 87.67 | ...66,76,86-89,115 - preparse.js | 100 | 100 | 100 | 100 | - packages/utils/dist | 90.95 | 81.98 | 91.95 | 90 | - array.js | 92.38 | 88.31 | 93.75 | 92.04 | 50,71-77 - async.js | 100 | 100 | 100 | 100 | - css.js | 61.11 | 44.44 | 66.66 | 56.25 | 15-26 - error.js | 90.9 | 0 | 100 | 90.9 | 6 - function.js | 88.88 | 100 | 75 | 87.5 | 9 - index.js | 100 | 100 | 100 | 100 | - memoization.js | 95.83 | 85.71 | 100 | 95.23 | 13,65 - object.js | 97.56 | 95.45 | 100 | 97.56 | 32 - options.js | 86.95 | 62.5 | 88.88 | 85.36 | 60-65,85 - string.js | 45.45 | 45.45 | 75 | 45.45 | 9,14-20 - symbol.js | 100 | 50 | 100 | 100 | 5 - tasks.js | 95.91 | 93.33 | 90 | 94.73 | 15-16 - packages/utils/dist/dom | 88.57 | 80.68 | 92 | 88.04 | - data.js | 90.62 | 80 | 100 | 100 | 29,34,45 - disposal.js | 89.39 | 80 | 83.33 | 87.93 | 58,82,91-96,102 - event.js | 80.85 | 57.57 | 83.33 | 77.5 | ...-46,50,54,78-83 - fixes.js | 100 | 100 | 100 | 100 | - html.js | 74.24 | 77.77 | 80 | 71.66 | ...,61,65-66,72,76 - info.js | 65.21 | 50 | 100 | 65.21 | 14-21,36,43 - manipulation.js | 100 | 81.81 | 100 | 100 | 18,36 - selectExtensions.js | 97.82 | 96.96 | 100 | 97.61 | 30 - virtualElements.js | 93.98 | 91.78 | 94.73 | 94.06 | 143-149,178-179 - packages/utils/helpers | 90 | 69.44 | 94.28 | 89.43 | - mocha-test-helpers.ts | 90 | 69.44 | 94.28 | 89.43 | ...165,177,231,256 diff --git a/bun.lock b/bun.lock index 0dbc5630a..37a4fc457 100644 --- a/bun.lock +++ b/bun.lock @@ -10,6 +10,7 @@ "@types/mocha": "^10.0.10", "@vitest/browser": "^4.1.4", "@vitest/browser-playwright": "^4.1.4", + "@vitest/coverage-v8": "^4.1.4", "chai": "^6.2.2", "esbuild": "^0.28.0", "happy-dom": "^20.9.0", @@ -315,8 +316,18 @@ }, }, "packages": { + "@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="], + + "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="], + + "@babel/parser": ["@babel/parser@7.29.2", "", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA=="], + "@babel/runtime": ["@babel/runtime@7.29.2", "", {}, "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g=="], + "@babel/types": ["@babel/types@7.29.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A=="], + + "@bcoe/v8-coverage": ["@bcoe/v8-coverage@1.0.2", "", {}, "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA=="], + "@biomejs/biome": ["@biomejs/biome@2.4.11", "", { "optionalDependencies": { "@biomejs/cli-darwin-arm64": "2.4.11", "@biomejs/cli-darwin-x64": "2.4.11", "@biomejs/cli-linux-arm64": "2.4.11", "@biomejs/cli-linux-arm64-musl": "2.4.11", "@biomejs/cli-linux-x64": "2.4.11", "@biomejs/cli-linux-x64-musl": "2.4.11", "@biomejs/cli-win32-arm64": "2.4.11", "@biomejs/cli-win32-x64": "2.4.11" }, "bin": { "biome": "bin/biome" } }, "sha512-nWxHX8tf3Opb/qRgZpBbsTOqOodkbrkJ7S+JxJAruxOReaDPPmPuLBAGQ8vigyUgo0QBB+oQltNEAvalLcjggA=="], "@biomejs/cli-darwin-arm64": ["@biomejs/cli-darwin-arm64@2.4.11", "", { "os": "darwin", "cpu": "arm64" }, "sha512-wOt+ed+L2dgZanWyL6i29qlXMc088N11optzpo10peayObBaAshbTcxKUchzEMp9QSY8rh5h6VfAFE3WTS1rqg=="], @@ -431,8 +442,12 @@ "@inquirer/external-editor": ["@inquirer/external-editor@1.0.3", "", { "dependencies": { "chardet": "^2.1.1", "iconv-lite": "^0.7.0" }, "peerDependencies": { "@types/node": ">=18" } }, "sha512-RWbSrDiYmO4LbejWY7ttpxczuwQyZLBUyygsA9Nsv95hpzUWwnNTVQmAq3xuh7vNwCp07UTmE5i11XAEExx4RA=="], + "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="], + "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="], + "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], + "@manypkg/find-root": ["@manypkg/find-root@1.1.0", "", { "dependencies": { "@babel/runtime": "^7.5.5", "@types/node": "^12.7.1", "find-up": "^4.1.0", "fs-extra": "^8.1.0" } }, "sha512-mki5uBvhHzO8kYYix/WRy2WX8S3B5wdVSc9D6KcU5lQNglP2yt58/VfLuAK49glRXChosY8ap2oJ1qgma3GUVA=="], "@manypkg/get-packages": ["@manypkg/get-packages@1.1.3", "", { "dependencies": { "@babel/runtime": "^7.5.5", "@changesets/types": "^4.0.1", "@manypkg/find-root": "^1.1.0", "fs-extra": "^8.1.0", "globby": "^11.0.0", "read-yaml-file": "^1.1.0" } }, "sha512-fo+QhuU3qE/2TQMQmbVMqaQ6EWbMhi4ABWP+O4AM1NqPBuy0OrApV5LO6BrrgnhtAHS2NH6RrVk9OL181tTi8A=="], @@ -645,6 +660,8 @@ "@vitest/browser-playwright": ["@vitest/browser-playwright@4.1.4", "", { "dependencies": { "@vitest/browser": "4.1.4", "@vitest/mocker": "4.1.4", "tinyrainbow": "^3.1.0" }, "peerDependencies": { "playwright": "*", "vitest": "4.1.4" } }, "sha512-q3PchVhZINX23Pv+RERgAtDlp6wzVkID/smOPnZ5YGWpeWUe3jMNYppeVh15j4il3G7JIJty1d1Kicpm0HSMig=="], + "@vitest/coverage-v8": ["@vitest/coverage-v8@4.1.4", "", { "dependencies": { "@bcoe/v8-coverage": "^1.0.2", "@vitest/utils": "4.1.4", "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" }, "peerDependencies": { "@vitest/browser": "4.1.4", "vitest": "4.1.4" }, "optionalPeers": ["@vitest/browser"] }, "sha512-x7FptB5oDruxNPDNY2+S8tCh0pcq7ymCe1gTHcsp733jYjrJl8V1gMUlVysuCD9Kz46Xz9t1akkv08dPcYDs1w=="], + "@vitest/expect": ["@vitest/expect@4.1.4", "", { "dependencies": { "@standard-schema/spec": "^1.1.0", "@types/chai": "^5.2.2", "@vitest/spy": "4.1.4", "@vitest/utils": "4.1.4", "chai": "^6.2.2", "tinyrainbow": "^3.1.0" } }, "sha512-iPBpra+VDuXmBFI3FMKHSFXp3Gx5HfmSCE8X67Dn+bwephCnQCaB7qWK2ldHa+8ncN8hJU8VTMcxjPpyMkUjww=="], "@vitest/mocker": ["@vitest/mocker@4.1.4", "", { "dependencies": { "@vitest/spy": "4.1.4", "estree-walker": "^3.0.3", "magic-string": "^0.30.21" }, "peerDependencies": { "msw": "^2.4.9", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" }, "optionalPeers": ["msw", "vite"] }, "sha512-R9HTZBhW6yCSGbGQnDnH3QHfJxokKN4KB+Yvk9Q1le7eQNYwiCyKxmLmurSpFy6BzJanSLuEUDrD+j97Q+ZLPg=="], @@ -669,6 +686,8 @@ "assertion-error": ["assertion-error@2.0.1", "", {}, "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA=="], + "ast-v8-to-istanbul": ["ast-v8-to-istanbul@1.0.0", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.31", "estree-walker": "^3.0.3", "js-tokens": "^10.0.0" } }, "sha512-1fSfIwuDICFA4LKkCzRPO7F0hzFf0B7+Xqrl27ynQaa+Rh0e1Es0v6kWHPott3lU10AyAr7oKHa65OppjLn3Rg=="], + "better-path-resolve": ["better-path-resolve@1.0.0", "", { "dependencies": { "is-windows": "^1.0.0" } }, "sha512-pbnl5XzGBdrFU/wT4jqmJVPn2B6UHPBOhzMQkY/SPUPB6QtUXtmBHBIwCbXJol93mOpGMnQyP/+BB19q04xj7g=="], "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="], @@ -733,6 +752,10 @@ "happy-dom": ["happy-dom@20.9.0", "", { "dependencies": { "@types/node": ">=20.0.0", "@types/whatwg-mimetype": "^3.0.2", "@types/ws": "^8.18.1", "entities": "^7.0.1", "whatwg-mimetype": "^3.0.0", "ws": "^8.18.3" } }, "sha512-GZZ9mKe8r646NUAf/zemnGbjYh4Bt8/MqASJY+pSm5ZDtc3YQox+4gsLI7yi1hba6o+eCsGxpHn5+iEVn31/FQ=="], + "has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="], + + "html-escaper": ["html-escaper@2.0.2", "", {}, "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg=="], + "human-id": ["human-id@4.1.3", "", { "bin": "dist/cli.js" }, "sha512-tsYlhAYpjCKa//8rXZ9DqKEawhPoSytweBC2eNvcaDK+57RZLHGqNs3PZTQO6yekLFSuvA6AlnAfrw1uBvtb+Q=="], "iconv-lite": ["iconv-lite@0.7.2", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw=="], @@ -751,10 +774,18 @@ "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="], + "istanbul-lib-coverage": ["istanbul-lib-coverage@3.2.2", "", {}, "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg=="], + + "istanbul-lib-report": ["istanbul-lib-report@3.0.1", "", { "dependencies": { "istanbul-lib-coverage": "^3.0.0", "make-dir": "^4.0.0", "supports-color": "^7.1.0" } }, "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw=="], + + "istanbul-reports": ["istanbul-reports@3.2.0", "", { "dependencies": { "html-escaper": "^2.0.0", "istanbul-lib-report": "^3.0.0" } }, "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA=="], + "jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="], "jquery": ["jquery@4.0.0", "", {}, "sha512-TXCHVR3Lb6TZdtw1l3RTLf8RBWVGexdxL6AC8/e0xZKEpBflBsjh9/8LXw+dkNFuOyW9B7iB3O1sP7hS0Kiacg=="], + "js-tokens": ["js-tokens@10.0.0", "", {}, "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q=="], + "js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": "bin/js-yaml.js" }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="], "jsonfile": ["jsonfile@4.0.0", "", { "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg=="], @@ -791,6 +822,10 @@ "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], + "magicast": ["magicast@0.5.2", "", { "dependencies": { "@babel/parser": "^7.29.0", "@babel/types": "^7.29.0", "source-map-js": "^1.2.1" } }, "sha512-E3ZJh4J3S9KfwdjZhe2afj6R9lGIN5Pher1pF39UGrXRqq/VDaGVIGN13BjHd2u8B61hArAGOnso7nBOouW3TQ=="], + + "make-dir": ["make-dir@4.0.0", "", { "dependencies": { "semver": "^7.5.3" } }, "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw=="], + "merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="], "micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="], @@ -899,6 +934,8 @@ "strip-json-comments": ["strip-json-comments@5.0.3", "", {}, "sha512-1tB5mhVo7U+ETBKNf92xT4hrQa3pm0MZ0PQvuDnWgAAGHDsfp4lPSpiS6psrSiet87wyGPh9ft6wmhOMQ0hDiw=="], + "supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], + "term-size": ["term-size@2.2.1", "", {}, "sha512-wK0Ri4fOGjv/XPy8SBHZChl8CM7uMc5VML7SqiQ0zG7+J5Vr+RMQDoHa2CNT6KHUnTGIXH34UDMkPzAUyapBZg=="], "tinybench": ["tinybench@2.9.0", "", {}, "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg=="], diff --git a/package.json b/package.json index d808b5a2f..b0a387eaa 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "build": "bun run --filter './packages/*' build && bun run --filter './builds/*' build", "test": "bunx @biomejs/biome ci . && bun run build && bunx vitest run", "test:ff": "bunx @biomejs/biome ci . && bun run build && VITEST_BROWSERS=firefox bunx vitest run", + "test:coverage": "bun run build && bunx vitest run --coverage --project browser", "tsc": "bunx tsc", "dts": "bunx tsc --build tsconfig.dts.json", "format": "bunx @biomejs/biome format .", @@ -36,6 +37,7 @@ "@types/mocha": "^10.0.10", "@vitest/browser": "^4.1.4", "@vitest/browser-playwright": "^4.1.4", + "@vitest/coverage-v8": "4.1.4", "chai": "^6.2.2", "esbuild": "^0.28.0", "happy-dom": "^20.9.0", diff --git a/tools/build.ts b/tools/build.ts index 2abac21f9..005cab893 100644 --- a/tools/build.ts +++ b/tools/build.ts @@ -11,7 +11,7 @@ const { name, version } = pkg const banner = `// ${name} 🥊 ${version}` const { buildMode = 'default', iifeGlobalName = 'tko' } = pkg.tko ?? {} -const common = `--log-level=warning --define:BUILD_VERSION='"${version}"' --sourcemap=external` +const common = `--log-level=warning --define:BUILD_VERSION='"${version}"' --sourcemap=linked` function esbuild(args: string) { console.log(`[build] ${name} → ${args.match(/--out(?:file|dir)=(\S+)/)?.[1] ?? ''}`) diff --git a/vitest.config.ts b/vitest.config.ts index 5b643a24a..80d48aa6b 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -11,6 +11,44 @@ export default defineConfig({ test: { testTimeout: 10000, globals: true, + coverage: { + // V8 coverage runs against the `browser` project (Playwright Chromium, + // V8 runtime). Firefox/WebKit are not covered — JavaScriptCore and + // SpiderMonkey don't expose V8's coverage protocol. The authoritative + // real-browser matrix is unaffected; coverage is opt-in via + // `bun run test:coverage`. + provider: 'v8', + reporter: ['text', 'text-summary', 'html', 'lcov', 'json-summary'], + reportsDirectory: 'coverage', + // Tests load each `@tko/*` package via its `exports` (compiled `dist/`). + // We include both `dist/` (so v8 picks up execution) and `src/` (so + // source-map remapping can surface the original TS files in the report). + // builds/*/dist/browser.min.js is what tests actually load + // (vitest-setup imports it as the IIFE entry); v8 instruments + // it and source-map remap surfaces builds/*/src/index.ts in the + // report. Other dist files (browser.js, index.cjs/mjs, common.js) + // are alternate output formats not exercised by the test runner, + // so we exclude them to avoid 0% rows. + include: [ + 'packages/*/src/**/*.ts', + 'packages/*/dist/**/*.js', + 'builds/*/src/**/*.ts', + 'builds/*/dist/browser.min.js' + ], + // `packages/*/index.ts` are 1-line re-export barrels — exclude them. + // `builds/*/src/index.ts` is the only real source file in each build, + // so we anchor the index-exclude to packages/ only. + exclude: [ + '**/spec/**', + '**/helpers/**', + '**/types/**', + '**/*.d.ts', + '**/*.cjs', + 'packages/*/index.ts', + 'packages/*/index.js' + ], + clean: true + }, projects: [ // Authoritative real-browser matrix — UNCHANGED. Always runs every spec. {