You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This section covers everything you need to verify your changes work correctly **before opening a PR**. There are two layers of testing: visual browser preview and the automated test suite.
94
+
Before opening a PR, every contributor is expected to verify their changes locally across three areas: **visual SVG output**, **unit tests**, and **branch coverage**. This section explains how to do all three.
95
95
96
96
### 1. Visual Browser Preview
97
97
98
-
With your dev server running (`npm run dev`), open your browser and visit these URLs to preview the SVG output directly:
98
+
With your dev server running (`npm run dev`), open your browser and visit these URLs to preview the SVG output directly — **open them as raw URLs, not embedded in an `<img>` tag or Markdown preview**:
-**Inspect SVG structure:** Open DevTools (`Cmd+Option+I` / `F12`) → Elements panel → expand the `<svg>` node to inspect every path, rect, and text element
133
+
-**Force a fresh fetch:** Add `&refresh=true` to bypass the cache: `http://localhost:3000/api/streak?user=YOUR_GITHUB_USERNAME&refresh=true`
134
+
-**Test different params live:**`?theme=obsidian`, `?size=large`, `?grace=2`, `?autoTheme=true`
135
+
130
136
> **⚠️ Browser XML error warning:** If your browser shows an `EntityRef: expecting ';'` error instead of rendering the SVG, it means an unescaped `&` character exists somewhere in the SVG output. All `&` characters inside SVG `<style>` blocks (e.g. in Google Fonts `@import` URLs) must be written as `&`. Check `lib/svg/generator.ts` for any raw `&` in template literals.
131
137
132
138
> **💡 Tip:** For a cleaner SVG preview, open the URL in **Firefox** — it renders SVG directly in the browser with no wrapper page. Chrome wraps it in an XML viewer which can show false parse warnings.
133
139
134
-
### 2. Running the Vitest Test Suite
140
+
### 2. Offline SVG Inspection with SVG Viewer
141
+
142
+
When you cannot run a dev server — or you want to inspect the raw SVG geometry in isolation — use **[SVG Viewer](https://www.svgviewer.dev/)**.
143
+
144
+
**Step-by-step:**
145
+
146
+
1. With the dev server running, open the badge URL in your browser
147
+
2. Right-click anywhere on the page → **View Page Source** (`Cmd+U` / `Ctrl+U`)
148
+
3. Select all (`Cmd+A`) → Copy (`Cmd+C`)
149
+
4. Go to [https://www.svgviewer.dev/](https://www.svgviewer.dev/)
150
+
5. Paste the SVG source into the left panel — the right panel renders it instantly
151
+
152
+
**What to check in SVG Viewer:**
153
+
154
+
- Tower geometry is correctly positioned on the isometric grid
155
+
- No overlapping paths or misaligned elements
156
+
- Text labels are readable and not clipped by the SVG boundary
157
+
- Glow filter (`<feGaussianBlur>`) renders at the correct intensity
158
+
- The `<title>` accessibility tag is present on every tower group (required by CONTRIBUTING.md a11y rules)
159
+
160
+
### 3. Running the Vitest Test Suite
135
161
136
162
CommitPulse uses **Vitest** for unit and integration tests. Run the full test suite with:
137
163
138
164
```bash
165
+
# Run all tests once (use before committing)
139
166
npm run test
140
-
```
141
167
142
-
To run tests in watch mode while you develop (reruns on every file save):
143
-
144
-
```bash
168
+
# Re-run automatically on every file save (use during active development)
145
169
npm run test -- --watch
146
-
```
147
170
148
-
To run only a specific test file:
149
-
150
-
```bash
171
+
# Run only a specific test file in isolation (fastest way to debug a failure)
151
172
npm run test -- lib/calculate.test.ts
173
+
174
+
# Run only tests whose name matches a pattern
175
+
npm run test -- --reporter=verbose -t "generateVersusSVG"
152
176
```
153
177
154
178
**What a passing run looks like:**
155
179
156
180
```
157
-
✓ lib/calculate.test.ts (12 tests)
158
-
✓ lib/svg/generator.test.ts (8 tests)
159
-
✓ app/api/streak/route.test.ts (6 tests)
181
+
✓ lib/calculate.test.ts (43 tests)
182
+
✓ lib/svg/generator.test.ts (19 tests)
183
+
✓ app/api/streak/route.test.ts (112 tests)
160
184
161
-
Test Files 3 passed (3)
162
-
Tests 26 passed (26)
185
+
Test Files 70 passed (70)
186
+
Tests 819 passed (819)
163
187
```
164
188
165
189
> **🚨 All tests must pass before you open a PR.** The CI pipeline runs `npm run test` automatically on every pull request and will block merging if any test fails.
166
190
167
-
### 3. Interpreting SVG Output in the Browser
191
+
### 4. Interpreting Vitest Output
192
+
193
+
Vitest output can look intimidating at first. Here is how to read it:
194
+
195
+
```
196
+
FAIL lib/svg/generator.test.ts
197
+
198
+
AssertionError: expected '<svg width="600"…' to include 'width="100%"'
@@ -177,15 +221,61 @@ When you open a badge URL in your browser, here is what each response means:
177
221
| Browser XML parse error / blank white page | ❌ Bug — unescaped `&` or malformed SVG in generator |
178
222
|`401 Unauthorized` in the terminal | ❌ Your `GITHUB_PAT` in `.env.local` is missing or invalid |
179
223
180
-
### 4. Lint and Format
224
+
### 5. Checking Branch Coverage Before Pushing
225
+
226
+
The CI pipeline enforces a **minimum 70% branch coverage** across all `lib/` files. If your PR drops coverage below this threshold, it will be **blocked from merging automatically** — no exceptions.
227
+
228
+
Always run the coverage report locally before pushing:
|`% Branch`|**≥ 70%**| Every `if/else`, ternary, `??`, and `&&` path covered by at least one test |
248
+
|`% Funcs`| ≥ 70% | Every exported function called at least once in tests |
249
+
|`% Stmts`| ≥ 70% | Every statement executed at least once |
250
+
|`Uncovered Line #s`| — | Exact lines with no test coverage — start here when fixing gaps |
251
+
252
+
**The most important file:**`lib/svg/generator.ts` has the most complex branch logic (auto-theme vs static, size scaling, particle generation, ghost city mode, versus mode). If you add any new `if/else` or ternary logic here, you **must** add tests that exercise both branches — otherwise coverage will drop and block your PR.
253
+
254
+
**If coverage drops below 70%:**
255
+
256
+
1. Look at the `Uncovered Line #s` column for the failing file
257
+
2. Open the file and find those exact lines
258
+
3. Identify which branch condition is untested (the `if` path? the `else`? a ternary fallback?)
259
+
4. Add a test that reaches that code path
260
+
5. Re-run `npm run test:coverage` and confirm the percentage recovered
261
+
262
+
> **Tip:** The coverage report is also generated as an HTML file. Open it in your browser for a visual, line-by-line view of exactly which branches are hit (green) and which are missed (red):
263
+
264
+
```bash
265
+
open coverage/index.html # macOS
266
+
xdg-open coverage/index.html # Linux
267
+
start coverage/index.html # Windows
268
+
```
269
+
270
+
### 6. Lint and Format
181
271
182
272
Run these before every commit:
183
273
184
274
```bash
185
-
# Auto-format all files
275
+
# Auto-format all files to match the project's Prettier config
186
276
npm run format
187
277
188
-
# Check for linting errors
278
+
# Check for any remaining linting errors
189
279
npm run lint
190
280
```
191
281
@@ -386,6 +476,7 @@ Use the following format: `type/short-description`
-[ ] I have tested these changes locally (`localhost:3000/api/streak?user=YOUR_USERNAME`).
435
527
-[ ] I have run `npm run format` and `npm run lint` locally and resolved all errors (CI will fail otherwise).
528
+
-[ ] I have run `npm run test` and all tests pass locally.
529
+
-[ ] I have run `npm run test:coverage` and branch coverage is at or above 70%.
436
530
-[ ] My commits follow the Conventional Commits format (e.g., `feat(themes): ...`, `fix(calculate): ...`).
437
531
-[ ] I have updated `README.md` if I added a new theme or URL parameter.
438
532
-[ ] I have started the repo.
@@ -464,10 +558,29 @@ npm run lint
464
558
465
559
# 3. Ensure all tests pass
466
560
npm run test
561
+
562
+
# 4. Verify branch coverage stays at or above 70%
563
+
npm run test:coverage
467
564
```
468
565
469
566
Fix every error before pushing. If you add new logic or features, you are expected to write tests for them.
470
567
568
+
### Coverage Threshold
569
+
570
+
The CI pipeline enforces a **minimum 70% branch coverage** across all measured files. This is not optional — PRs that drop coverage below this threshold are automatically blocked from merging.
571
+
572
+
The threshold exists because branch coverage (covering every `if/else`, ternary, and `??` path) is the most reliable indicator that edge cases are tested. Statement coverage alone is not enough.
If you add a new `if/else`, ternary, or optional-chain (`?.`) anywhere in `lib/`, you must add a test covering both the truthy and falsy paths before pushing.
583
+
471
584
### Testing Guidelines
472
585
473
586
We use **Vitest** alongside **React Testing Library** for our test suite.
@@ -479,7 +592,7 @@ We use **Vitest** alongside **React Testing Library** for our test suite.
479
592
-**Humanic Comments:** Comments in test files should explain _why_ a test exists or what specific edge-case it covers, rather than just repeating what the code does line-by-line.
480
593
481
594
> **🚨 GitHub Actions CI Gate**
482
-
> Our CI pipeline runs `npm run lint`, `npm run format --check`, and `npm run test` automatically on **every pull request**. If your code fails any check, **the PR will be blocked from merging** until the issues are resolved. There is no way to bypass this gate — so run the commands locally first and save yourself the round-trip.
595
+
> Our CI pipeline runs `npm run lint`, `npm run format --check`, `npm run test`, and `npm run test:coverage` automatically on **every pull request**. If your code fails any check, **the PR will be blocked from merging** until the issues are resolved. There is no way to bypass this gate — so run the commands locally first and save yourself the round-trip.
|`tz`|`string`| No | Omitted = UTC | IANA timezone (e.g. `Asia/Kolkata`, `America/New_York`) — aligns "today" with the user local midnight. Note: `?tz=UTC` is valid but cached separately from omitting `tz`. |
178
178
|`lang`|`string`| No |`en`| Language code for labels (`en`, `es`, `hi`, `fr`, `pt`, `ko`, `ja`, `de`, `zh`) |
179
179
|`view`|`string`| No |`default`| Rendering mode: `default` (3D Monolith), `monthly` (Compact monthly stats), or `heatmap` (flat 2D contribution heatmap) |
180
+
|`entrance`|`string`| No |`rise`| Entrance animation for towers: `rise` (default), `fade`, `slide`, or `none`. |
180
181
|`delta_format`|`string`| No |`percent`| Format for month-over-month delta in monthly view: `percent` (e.g. +12%), `absolute` (e.g. +15 commits), or `both`|
181
182
|`width`|`number`| No |`300`| Custom width for the SVG canvas (currently only applies to `view=monthly`) |
182
183
|`height`|`number`| No |`120`| Custom height for the SVG canvas (currently only applies to `view=monthly`) |
|`labelColor`|`hex`| No | — | Custom text color for the isometric labels — **without**`#`|
189
190
|`versus`|`string`| No | — | GitHub username of an opponent to compare against in side-by-side versus mode |
190
191
|`shading`|`boolean`| No |`false`| Apply intensity-based opacity shading to tower faces so lower intensity levels appear slightly dimmer |
192
+
|`opacity`|`number`| No |`1.0`| Global opacity scalar for all tower fill-opacity values (0.1–1.0). `opacity=0.5` = semi-transparent ghost look. `opacity=0.8` = faded, great on light backgrounds. |
191
193
|`gradient`|`boolean`| No |`false`| Opt-in to show volumetric gradients on the monolith floor |
|`obsidian`| Deep charcoal & amber gold |`1a1a2e`|`f59e0b`|`e2e8f0`|
223
226
224
227
> **`auto` uses CSS `@media (prefers-color-scheme)`** inside the SVG so the badge switches between the `light` and `dark` palettes based on the viewer's OS setting — no JavaScript required. This is ideal for GitHub profile READMEs where visitors may use either mode.
225
228
@@ -320,6 +323,14 @@ Explore some of the built-in CommitPulse themes and quickly copy the style you l
0 commit comments