From 09fcb9756d80d0445b571726d39775fa5d11a826 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Fri, 18 Sep 2026 08:04:44 +0000 Subject: [PATCH 1/2] test: add coverage reporting Add npm run test:coverage using Node's built-in test coverage, persist the per-file table, and upload it as a CI artifact. No percentage gate. Co-authored-by: David --- .github/workflows/ci.yml | 12 ++++++-- CHANGELOG.md | 1 + CONTRIBUTING.md | 4 ++- docs/development.md | 48 +++++++++++++++++++++++++++++-- package.json | 3 +- scripts/coverage-report.js | 59 ++++++++++++++++++++++++++++++++++++++ 6 files changed, 121 insertions(+), 6 deletions(-) create mode 100644 scripts/coverage-report.js diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6da89e0..be1d4fd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,8 +28,16 @@ jobs: - name: Install dependencies run: npm ci - - name: Run tests - run: npm test + - name: Run tests with coverage + run: npm run test:coverage + + - name: Upload coverage report + if: always() + uses: actions/upload-artifact@v4 + with: + name: coverage-report + path: coverage/coverage.txt + if-no-files-found: warn - name: Build extension run: npm run build:extension diff --git a/CHANGELOG.md b/CHANGELOG.md index e057c21..9b2b9c6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ extension (`extension/manifest.json`). ### Added - Open-source contributor docs and GitHub community files (issues, PRs, CI, code of conduct) +- `npm run test:coverage` (Node built-in coverage) and a CI `coverage-report` artifact; no percentage gate ### Changed diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4d76452..0a77e9e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -96,7 +96,9 @@ you touch the popup, options page, or landing site: - If you changed the landing site, check `landing/index.html` and `landing/privacy.html`. If you add or change API behavior, add or update a test under `tests/api/` or -`tests/unit/` rather than relying on manual checks alone. +`tests/unit/` rather than relying on manual checks alone. To see which files +the suite exercises, run `npm run test:coverage`. How to read the table and +the current baseline are in [docs/development.md](docs/development.md). ### 7. Open a pull request diff --git a/docs/development.md b/docs/development.md index ba9ae04..c21654c 100644 --- a/docs/development.md +++ b/docs/development.md @@ -84,6 +84,7 @@ Vercel: set Root Directory to `landing`, framework Other, empty build and output | `npm start` | Production server | | `npm test` | Automated test suite (`node --test`) | | `npm run test:watch` | Automated test suite in watch mode | +| `npm run test:coverage` | Same suite plus Node's built-in coverage report | | `npm run build:extension` | Bundle unpacked extension to `dist/extension/` | ## API surface @@ -124,12 +125,55 @@ database). It covers: Tests run against a throwaway MongoDB started in memory by `mongodb-memory-server` - no local MongoDB, `MONGO_URI`, or other setup is -needed, and your dev/production data is never touched. CI runs `npm test` and -`npm run build:extension` on every push and pull request. +needed, and your dev/production data is never touched. CI runs +`npm run test:coverage` and `npm run build:extension` on every push and pull +request. Coverage is informational: CI does not fail on a percentage. The +text report is uploaded as the `coverage-report` workflow artifact +(`coverage/coverage.txt`). There is still no end-to-end UI suite. Manually check the flow you changed in the loaded unpacked extension. +### Coverage + +```bash +npm run test:coverage +``` + +This wraps `node --test --experimental-test-coverage` (no extra dependency; +see `scripts/coverage-report.js`). After the suite finishes, Node prints a +per-file table and writes the same output to `coverage/coverage.txt` +(gitignored). + +How to read the table: + +| Column | Meaning | +|--------|---------| +| `file` | Source file the runner loaded | +| `line %` | Share of executable lines that ran | +| `branch %` | Share of branches (if/else, ternaries, and similar) that ran | +| `funcs %` | Share of functions that were called | +| `uncovered lines` | Line numbers or ranges the suite never hit | + +The `all files` row is the headline number. Files the suite does not load +(the extension popup, landing site, most Express HTML routes) do not appear. + +**Baseline** (refreshed with this change on Node 22; CI is Node 20, so expect +small drift): + +| | line % | branch % | funcs % | +|---|--------|----------|---------| +| **all files** | **67** | **83** | **63** | + +- At 100% lines: `routes/api/*`, `middleware/apiAuth.js`, `middleware/validateId.js`, `models/collection.js`, `models/user.js`, `joiSchema.js`. +- Partial, because the suite does not hit every handler: `controllers/api/auth.js` (~44% lines), `controllers/api/bookmarks.js` (~63%), `controllers/api/collections.js` (~68%), `models/bookmark.js` (~85%). +- Still thin: `utils/mailer.js` (~9% lines, 0% functions), `utils/passwordReset.js` (50% lines, 0% functions), `utils/friendlyError.js` (~51% lines, ~42% branches). +- Express HTML controllers (`controllers/user.js`, `controllers/bookmarks.js`, and the rest) are mostly unused by this suite. Extension and landing code are not loaded, so they do not appear. + +The table includes test files the runner loaded. That baseline is a snapshot, +not a gate. Raise it by adding tests, not by adding a threshold. Coverage +needs Node 18.15 or newer (`--experimental-test-coverage`). + ## Project map See [architecture.md](architecture.md). diff --git a/package.json b/package.json index cb80585..0d66280 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,8 @@ "build": "npm run build:extension", "build:extension": "node scripts/build-extension.js", "test": "node --test", - "test:watch": "node --test --watch" + "test:watch": "node --test --watch", + "test:coverage": "node scripts/coverage-report.js" }, "keywords": [ "bookmarks", diff --git a/scripts/coverage-report.js b/scripts/coverage-report.js new file mode 100644 index 0000000..6cecfea --- /dev/null +++ b/scripts/coverage-report.js @@ -0,0 +1,59 @@ +#!/usr/bin/env node +/** + * Run the Node test runner with built-in coverage and persist the report. + * + * Uses `node --test --experimental-test-coverage` (no extra coverage + * dependency). Exit status follows the tests themselves — this script + * does not enforce a coverage threshold. + * + * Named coverage-report.js on purpose: Node's default test globs include + * test-*.js, so a file called test-coverage.js would be picked up as a test. + * + * Writes the full runner output (including the per-file table) to + * coverage/coverage.txt so CI can upload it as an artifact. + */ + +const { spawn } = require('child_process'); +const fs = require('fs'); +const path = require('path'); + +const ROOT = path.join(__dirname, '..'); +const OUT_DIR = path.join(ROOT, 'coverage'); +const OUT_FILE = path.join(OUT_DIR, 'coverage.txt'); + +fs.mkdirSync(OUT_DIR, { recursive: true }); + +const chunks = []; +const child = spawn( + process.execPath, + ['--test', '--experimental-test-coverage', '--test-reporter=spec'], + { + cwd: ROOT, + env: process.env, + stdio: ['inherit', 'pipe', 'pipe'], + } +); + +function forward(stream, dest) { + stream.on('data', (chunk) => { + chunks.push(chunk); + dest.write(chunk); + }); +} + +forward(child.stdout, process.stdout); +forward(child.stderr, process.stderr); + +child.on('error', (err) => { + console.error(err); + process.exit(1); +}); + +child.on('close', (code, signal) => { + fs.writeFileSync(OUT_FILE, Buffer.concat(chunks)); + if (signal) { + process.kill(process.pid, signal); + return; + } + process.exit(code ?? 1); +}); From 2b15a65d04db026b4c1b47b68985f4a4ee482190 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Fri, 18 Sep 2026 08:07:09 +0000 Subject: [PATCH 2/2] docs: note coverage baseline matches Node 20 CI The all-files totals are the same locally and in CI, so drop the unnecessary drift caveat. Co-authored-by: David --- docs/development.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/development.md b/docs/development.md index c21654c..b3881ca 100644 --- a/docs/development.md +++ b/docs/development.md @@ -158,8 +158,8 @@ How to read the table: The `all files` row is the headline number. Files the suite does not load (the extension popup, landing site, most Express HTML routes) do not appear. -**Baseline** (refreshed with this change on Node 22; CI is Node 20, so expect -small drift): +**Baseline** (refreshed with this change; same totals on Node 20 in CI and +Node 22 locally): | | line % | branch % | funcs % | |---|--------|----------|---------|