Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
4 changes: 3 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
48 changes: 46 additions & 2 deletions docs/development.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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; same totals on Node 20 in CI and
Node 22 locally):

| | 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).
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
59 changes: 59 additions & 0 deletions scripts/coverage-report.js
Original file line number Diff line number Diff line change
@@ -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);
});
Loading