Skip to content

Add vitest coverage for router, pages, and tool renderers#86

Merged
wpak-ai merged 5 commits into
masterfrom
test/frontend-render-routing-coverage
Jun 16, 2026
Merged

Add vitest coverage for router, pages, and tool renderers#86
wpak-ai merged 5 commits into
masterfrom
test/frontend-render-routing-coverage

Conversation

@clean6378-max-it

@clean6378-max-it clean6378-max-it commented Jun 16, 2026

Copy link
Copy Markdown
Collaborator

Closes #78

Summary

  • Add vitest suites for the frontend router (app.js), page modules (sessions, projects, search, export), and 3 tool renderers (bash, file_read, edit)
  • Lift static/js/ line coverage from ~31% to 85.5%
  • No production-code changes — tests only (DOM via jsdom, fetch mocked)

Sprint item

Closes Chen W3 #1

Test plan

  • npm test (local) — 72 tests, all green (was 39)
  • npm run test:coveragestatic/js/ 85.5% line coverage (before: ~31%)
  • CI js-tests matrix green (ubuntu + windows + macos)

New test files

File Covers
static/js/app.test.js Hash dispatch (#project/, #search, default), malformed URI fallback, hashchange → route, cached session → loadSession
static/js/sessions.test.js showWorkspace, loadSession, selectSession, copyAll
static/js/projects.test.js showProjects + empty state + API errors
static/js/search.test.js doSearch submit/render/empty/error + stale-request guard
static/js/export.test.js bulkExport success + 4xx/5xx, downloadSession file picker + blob fallback
static/js/render/tool_use/bash.test.js Bash tool-use DOM output
static/js/render/tool_result/file_read.test.js File-read tool-result summary
static/js/render/tool_use/edit.test.js Edit tool-use DOM output

Summary by CodeRabbit

  • Tests
    • Added Vitest coverage for routing, projects, search (including request staleness), sessions/workspace UI, export flows (bulk + session download), and tool result rendering (file read/edit/bash).
    • Included success, empty-state, and error-path assertions (including export toast messaging).
    • Added security-focused checks to ensure rendered HTML is properly escaped, plus clipboard and save-file export/fallback coverage.
  • CI
    • Updated the frontend pipeline to run the coverage-enabled test command and added minimum Vitest coverage thresholds.

@coderabbitai

coderabbitai Bot commented Jun 16, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 5ef24110-c62a-49e5-8b2b-ff232142b307

📥 Commits

Reviewing files that changed from the base of the PR and between 8b02a9d and d87612b.

📒 Files selected for processing (5)
  • .github/workflows/ci.yml
  • static/js/export.test.js
  • static/js/render/tool_use/edit.test.js
  • static/js/sessions.test.js
  • vitest.config.js
🚧 Files skipped from review as they are similar to previous changes (4)
  • .github/workflows/ci.yml
  • static/js/render/tool_use/edit.test.js
  • static/js/sessions.test.js
  • static/js/export.test.js

📝 Walkthrough

Walkthrough

Seven new Vitest test files are added under static/js/. They cover the hash-based router in app.js, the showProjects, showSearchPage/doSearch, and sessions workspace (showWorkspace, loadSession, selectSession, copyAll) page modules, the bulkExport/downloadSession export logic, and three tool renderer functions (renderBashUse, renderEditUse, renderFileReadResult). The vitest.config.js is updated to enforce a 50% line coverage threshold, and the CI workflow now runs coverage-enabled tests.

Changes

Frontend Test Coverage

Layer / File(s) Summary
Tool renderer unit tests
static/js/render/tool_use/bash.test.js, static/js/render/tool_use/edit.test.js, static/js/render/tool_result/file_read.test.js
Tests assert rendered HTML content, presence of tool-call markers, and HTML-escaping of unsafe input for renderBashUse, renderEditUse, and renderFileReadResult.
showProjects page tests
static/js/projects.test.js
Vitest suite with fixture data and fetch stubs covering successful project card rendering, empty-state UI, and API error message surfacing.
showSearchPage and doSearch tests
static/js/search.test.js
Tests cover page initialization with hash/DOM assertions, successful search with hit counts and snippets, empty and HTTP error states, and a concurrency/staleness case where older in-flight responses are discarded.
bulkExport and downloadSession tests
static/js/export.test.js
Tests verify POST payload shape, toast error rendering for 5xx and 4xx responses, blob write through mocked showSaveFilePicker writable, and fallback anchor-element download when file picker API is unavailable.
Sessions workspace tests
static/js/sessions.test.js
Route-specific fetch stubs exercise showWorkspace sidebar and active highlighting, loadSession message rendering and error handling, selectSession hash update, and copyAll clipboard write.
app.js router tests
static/js/app.test.js
Mocks all page modules, initializes the router via DOMContentLoaded, and asserts correct dispatch for default, #search, project, cached/uncached session, malformed URI, and repeated hashchange navigations.
Coverage configuration and CI update
vitest.config.js, .github/workflows/ci.yml
Vitest coverage configuration now enforces a 50% line coverage threshold, and the CI workflow runs npm run test:coverage instead of npm test.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • timon0305
  • wpak-ai

Poem

🐇 Hop hop, the tests now leap and bound,
Each route and render carefully found.
Mocked fetch and hash, the DOM reset clean,
No stale result shall taint the screen!
With Vitest suites the bugs shall flee—
Coverage climbs, and this bunny's free! 🌿

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: adding vitest coverage for router, pages, and tool renderers across eight new test files.
Linked Issues check ✅ Passed All acceptance criteria from issue #78 are met: router, page modules, and renderer test coverage added; line coverage increased from ~15% to 85.5% (exceeding ≥50% target); no production-code changes; cross-platform CI support included.
Out of Scope Changes check ✅ Passed All changes are in-scope test files addressing issue #78 objectives. The only non-test change is .github/workflows/ci.yml updating the test command to coverage mode, which is directly supporting the coverage measurement requirement.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch test/frontend-render-routing-coverage

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
static/js/render/tool_result/file_read.test.js (1)

4-24: ⚡ Quick win

Consider adding an HTML escaping test for consistency.

The bash.test.js and edit.test.js suites both include test cases that verify HTML special characters are escaped (e.g., <&lt;). For consistency and to verify the security contract, consider adding a similar test case here that checks whether special characters in file_path are properly escaped.

🧪 Example escaping test case
+
+    it('escapes HTML in file path', () => {
+        const html = renderFileReadResult({
+            result_type: 'file_read',
+            file_path: '<script>alert("xss")</script>',
+            num_lines: 10,
+        });
+        expect(html).not.toContain('<script>');
+        expect(html).toContain('&lt;script&gt;');
+    });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@static/js/render/tool_result/file_read.test.js` around lines 4 - 24, Add a
test case to the 'renderFileReadResult' describe block to verify that HTML
special characters in the file_path parameter are properly escaped in the
rendered output. Create a new test that passes a file_path containing characters
like `<`, `>`, `&`, or `"` to the renderFileReadResult function, and verify that
the returned HTML contains the properly escaped versions of those characters
(such as `&lt;`, `&gt;`, `&amp;`, `&quot;`). This ensures consistency with
similar escaping tests in bash.test.js and edit.test.js and verifies the
security contract of the function.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@static/js/render/tool_result/file_read.test.js`:
- Around line 4-24: Add a test case to the 'renderFileReadResult' describe block
to verify that HTML special characters in the file_path parameter are properly
escaped in the rendered output. Create a new test that passes a file_path
containing characters like `<`, `>`, `&`, or `"` to the renderFileReadResult
function, and verify that the returned HTML contains the properly escaped
versions of those characters (such as `&lt;`, `&gt;`, `&amp;`, `&quot;`). This
ensures consistency with similar escaping tests in bash.test.js and edit.test.js
and verifies the security contract of the function.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: dc46c31d-57df-4875-bf46-43412a248edc

📥 Commits

Reviewing files that changed from the base of the PR and between 0435b2d and 9f6fa32.

📒 Files selected for processing (8)
  • static/js/app.test.js
  • static/js/export.test.js
  • static/js/projects.test.js
  • static/js/render/tool_result/file_read.test.js
  • static/js/render/tool_use/bash.test.js
  • static/js/render/tool_use/edit.test.js
  • static/js/search.test.js
  • static/js/sessions.test.js

Enforce a 50% line-coverage floor in vitest.config.js so the
static/js/ gain cannot silently regress. Restore mocks after export
tests to prevent createElement spy leakage, and document fixture
shapes plus the search.js no-highlight behavior for future maintainers.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@vitest.config.js`:
- Around line 12-14: The coverage threshold configured in the vitest.config.js
thresholds object (lines 50% for lines) is not being enforced during CI because
the CI pipeline runs npm test in non-coverage mode. To fix this, add a separate
required CI job that explicitly runs coverage validation (typically via a
command like npm run test:coverage or vitest run --coverage) and configure it as
a required status check in your CI pipeline. This ensures the 50% coverage floor
is actually validated and blocks PRs when thresholds are not met, separate from
the regular test command.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 0e05b569-e233-43d1-8aa2-308ae408aa53

📥 Commits

Reviewing files that changed from the base of the PR and between 9f6fa32 and d04fea5.

📒 Files selected for processing (5)
  • static/js/export.test.js
  • static/js/projects.test.js
  • static/js/search.test.js
  • static/js/sessions.test.js
  • vitest.config.js
🚧 Files skipped from review as they are similar to previous changes (4)
  • static/js/projects.test.js
  • static/js/sessions.test.js
  • static/js/search.test.js
  • static/js/export.test.js

Comment thread vitest.config.js
…d coverage threshold in CI js-tests job

Run npm run test:coverage instead of npm test so vitest's 50% line
threshold in vitest.config.js is actually validated on every PR.
@clean6378-max-it

Copy link
Copy Markdown
Collaborator Author

@CodeRabbit, Can you review this PR in more depth again?

@coderabbitai

coderabbitai Bot commented Jun 16, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
echo "=== app.test.js ==="
cat static/js/app.test.js

echo ""
echo "=== export.test.js ==="
cat static/js/export.test.js

echo ""
echo "=== projects.test.js ==="
cat static/js/projects.test.js

echo ""
echo "=== sessions.test.js ==="
cat static/js/sessions.test.js

echo ""
echo "=== search.test.js ==="
cat static/js/search.test.js

echo ""
echo "=== bash.test.js ==="
cat static/js/render/tool_use/bash.test.js

echo ""
echo "=== file_read.test.js ==="
cat static/js/render/tool_result/file_read.test.js

echo ""
echo "=== edit.test.js ==="
cat static/js/render/tool_use/edit.test.js

echo ""
echo "=== vitest.config.js ==="
cat vitest.config.js

echo ""
echo "=== ci.yml ==="
cat .github/workflows/ci.yml

echo ""
echo "=== package.json (scripts/devdeps) ==="
cat package.json

Length of output: 32269


🏁 Script executed:

#!/bin/bash
echo "=== shared/state.js ==="
cat static/js/shared/state.js

echo ""
echo "=== export.js ==="
cat static/js/export.js

echo ""
echo "=== sessions.js (first 120 lines) ==="
head -120 static/js/sessions.js

echo ""
echo "=== search.js ==="
cat static/js/search.js

echo ""
echo "=== app.js ==="
cat static/js/app.js

echo ""
echo "=== render/tool_use/bash.js ==="
cat static/js/render/tool_use/bash.js

echo ""
echo "=== render/tool_use/edit.js ==="
cat static/js/render/tool_use/edit.js

echo ""
echo "=== render/tool_result/file_read.js ==="
cat static/js/render/tool_result/file_read.js

Length of output: 20099



@clean6378-max-it, here is an in-depth review of PR #86.


Deep Review: PR #86 — Vitest Frontend Coverage

Overall, the suite is well-structured, covers the stated scope, and the 85.5% line-coverage number is credible. The issues below range from potential test-reliability bugs to minor fragility concerns.


🔴 Issues that could cause silent false passes or hidden fragility

1. export.test.jsshowConfirm is not mocked

bulkExport calls showConfirm(label, callback) from ./shared/utils.js, which is never mocked. The confirmExport() helper clicks a real .confirm-ok DOM node rendered by the real showConfirm implementation:

async function confirmExport() {
    const ok = document.querySelector('.confirm-ok');   // relies on real showConfirm DOM
    ok.click();
    ...
}

If showConfirm's markup ever changes (e.g., the button class is renamed), all four bulkExport tests break with a cryptic Cannot read properties of null (reading 'click'). Consider adding an explicit mock:

vi.mock('./shared/utils.js', async (importOriginal) => {
    const actual = await importOriginal();
    return {
        ...actual,
        showConfirm: vi.fn((_, cb) => cb()),   // immediately invokes the callback
        showToast: vi.fn(),
    };
});

This would also make confirmExport() unnecessary.


2. projects.test.js — Inconsistent fetch mocking across tests

The first test stubs both /api/projects and /api/export/state. The second and third tests only stub /api/projects and Promise.reject on every other URL:

// Tests 2 & 3 — if showProjects calls /api/export/state here too…
return Promise.reject(new Error(`unexpected fetch: ${url}`));

If showProjects calls /api/export/state unconditionally (regardless of whether the project list is empty or the response is non-ok), the empty-state and error tests will throw and either surface an unhandled rejection or produce a misleading pass via a try/catch in the source. Align the mocks:

fetch.mockImplementation((url) => {
    if (url === '/api/projects') {
        return Promise.resolve({ ok: true, json: () => Promise.resolve([]) });
    }
    if (url === '/api/export/state') {
        return Promise.resolve({ ok: true, json: () => Promise.resolve({}) });
    }
    return Promise.reject(new Error(`unexpected fetch: ${url}`));
});

3. sessions.test.jscopyAll test does not await the clipboard write

copyAll();                                              // not awaited
expect(writeText).toHaveBeenCalledWith('Line one\nLine two');

copyAll calls navigator.clipboard.writeText() which returns a Promise. If the implementation is later refactored to await or chain off that Promise, this test will still pass (the synchronous writeText call happens before any await), but the assertion about what was written could race. The safer pattern:

await copyAll();                                        // or: await vi.waitFor(() => expect(writeText).toHaveBeenCalled())
expect(writeText).toHaveBeenCalledWith('Line one\nLine two');

🟡 Issues that reduce test isolation or maintainability

4. vi.stubGlobal not cleaned up in most test files

projects.test.js, sessions.test.js, and search.test.js all call vi.stubGlobal('fetch', vi.fn()) in beforeEach but have no afterEach to call vi.unstubAllGlobals(). While Vitest isolates module state per file, stub leakage between files in the same worker can occur in certain configurations. export.test.js correctly uses afterEach(() => vi.restoreAllMocks()) — apply the same pattern everywhere:

afterEach(() => {
    vi.unstubAllGlobals();
    vi.restoreAllMocks();
});

Similarly, app.test.js stubs window.scrollTo and Element.prototype.scrollIntoView in beforeAll and never restores them.


5. export.test.js — In-flight button state is not verified

The bulkExport shows progress then completes on success test description says "shows progress" but only checks the post-completion state:

expect(btn.textContent.trim()).toBe('Export all');
expect(btn.disabled).toBe(false);

The source clearly sets btn.disabled = true; btn.textContent = 'Exporting...' while the request is in flight. Consider verifying this intermediate state using vi.waitFor or by holding the fetch promise before resolving it.


6. sessions.test.js — Fixture ID mismatch

SESSION_DETAIL has session_id: 'sess-1', but the "marks the selected session active" test passes 'sess-2' to showWorkspace. The real loadSession is called with 'sess-2' and the mock returns the sess-1 detail object. The test still passes because it only asserts sidebar markup, not session content, but the inconsistency is misleading:

// Either update SESSION_DETAIL or add a SESSION_DETAIL_2 fixture
// so the loaded content matches the selected session ID.

🟢 Minor / low-severity notes

7. vitest.config.js — Only lines threshold gated

thresholds: {
    lines: 50,
}

Branch and function coverage can be significantly lower than line coverage (dead branches, uncalled code paths). Consider adding:

thresholds: {
    lines: 50,
    functions: 50,
    branches: 40,
}

8. file_read.test.js — Fragile negative assertion

expect(html).not.toContain('lines');

If finishToolResult's wrapper HTML ever gains a class name like "tool-lines", this assertion breaks spuriously. A tighter assertion would be:

expect(html).not.toMatch(/\d+ lines/);

9. search.test.js — Module-level lastSearchRequestId not reset

lastSearchRequestId in search.js is module-level and increments across tests. The stale-response test relies on relative ordering (first ID < second ID), so it works regardless of absolute values, but explicitly resetting it in beforeEach (via a reset export or vi.resetModules()) would make the test intent clearer and more robust if test ordering ever changes.


✅ What is working well

  • The router tests (app.test.js) cover all documented routing branches including cached-vs-cold session loading and malformed URI fallback.
  • The stale-request test in search.test.js correctly verifies the localRequestId guard.
  • The blob fallback test in export.test.js correctly derives the filename ('sess-abcdef12'.slice(0, 8)'session-sess-abc.md'), matching the source.
  • HTML escaping is tested in bash.test.js and edit.test.js.
  • CI enforcement via npm run test:coverage with the 50% threshold in vitest.config.js is well-wired.
  • The Rollup native binary workaround in ci.yml for cross-platform npm optional-dep issues is a solid addition.

@clean6378-max-it

Copy link
Copy Markdown
Collaborator Author

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain
🏁 Script executed:

#!/bin/bash
echo "=== app.test.js ==="
cat static/js/app.test.js

echo ""
echo "=== export.test.js ==="
cat static/js/export.test.js

echo ""
echo "=== projects.test.js ==="
cat static/js/projects.test.js

echo ""
echo "=== sessions.test.js ==="
cat static/js/sessions.test.js

echo ""
echo "=== search.test.js ==="
cat static/js/search.test.js

echo ""
echo "=== bash.test.js ==="
cat static/js/render/tool_use/bash.test.js

echo ""
echo "=== file_read.test.js ==="
cat static/js/render/tool_result/file_read.test.js

echo ""
echo "=== edit.test.js ==="
cat static/js/render/tool_use/edit.test.js

echo ""
echo "=== vitest.config.js ==="
cat vitest.config.js

echo ""
echo "=== ci.yml ==="
cat .github/workflows/ci.yml

echo ""
echo "=== package.json (scripts/devdeps) ==="
cat package.json

Length of output: 32269

🏁 Script executed:

#!/bin/bash
echo "=== shared/state.js ==="
cat static/js/shared/state.js

echo ""
echo "=== export.js ==="
cat static/js/export.js

echo ""
echo "=== sessions.js (first 120 lines) ==="
head -120 static/js/sessions.js

echo ""
echo "=== search.js ==="
cat static/js/search.js

echo ""
echo "=== app.js ==="
cat static/js/app.js

echo ""
echo "=== render/tool_use/bash.js ==="
cat static/js/render/tool_use/bash.js

echo ""
echo "=== render/tool_use/edit.js ==="
cat static/js/render/tool_use/edit.js

echo ""
echo "=== render/tool_result/file_read.js ==="
cat static/js/render/tool_result/file_read.js

Length of output: 20099

@clean6378-max-it, here is an in-depth review of PR #86.

Deep Review: PR #86 — Vitest Frontend Coverage

Overall, the suite is well-structured, covers the stated scope, and the 85.5% line-coverage number is credible. The issues below range from potential test-reliability bugs to minor fragility concerns.

🔴 Issues that could cause silent false passes or hidden fragility

1. export.test.jsshowConfirm is not mocked

bulkExport calls showConfirm(label, callback) from ./shared/utils.js, which is never mocked. The confirmExport() helper clicks a real .confirm-ok DOM node rendered by the real showConfirm implementation:

async function confirmExport() {
    const ok = document.querySelector('.confirm-ok');   // relies on real showConfirm DOM
    ok.click();
    ...
}

If showConfirm's markup ever changes (e.g., the button class is renamed), all four bulkExport tests break with a cryptic Cannot read properties of null (reading 'click'). Consider adding an explicit mock:

vi.mock('./shared/utils.js', async (importOriginal) => {
    const actual = await importOriginal();
    return {
        ...actual,
        showConfirm: vi.fn((_, cb) => cb()),   // immediately invokes the callback
        showToast: vi.fn(),
    };
});

This would also make confirmExport() unnecessary.

2. projects.test.js — Inconsistent fetch mocking across tests

The first test stubs both /api/projects and /api/export/state. The second and third tests only stub /api/projects and Promise.reject on every other URL:

// Tests 2 & 3 — if showProjects calls /api/export/state here too…
return Promise.reject(new Error(`unexpected fetch: ${url}`));

If showProjects calls /api/export/state unconditionally (regardless of whether the project list is empty or the response is non-ok), the empty-state and error tests will throw and either surface an unhandled rejection or produce a misleading pass via a try/catch in the source. Align the mocks:

fetch.mockImplementation((url) => {
    if (url === '/api/projects') {
        return Promise.resolve({ ok: true, json: () => Promise.resolve([]) });
    }
    if (url === '/api/export/state') {
        return Promise.resolve({ ok: true, json: () => Promise.resolve({}) });
    }
    return Promise.reject(new Error(`unexpected fetch: ${url}`));
});

3. sessions.test.jscopyAll test does not await the clipboard write

copyAll();                                              // not awaited
expect(writeText).toHaveBeenCalledWith('Line one\nLine two');

copyAll calls navigator.clipboard.writeText() which returns a Promise. If the implementation is later refactored to await or chain off that Promise, this test will still pass (the synchronous writeText call happens before any await), but the assertion about what was written could race. The safer pattern:

await copyAll();                                        // or: await vi.waitFor(() => expect(writeText).toHaveBeenCalled())
expect(writeText).toHaveBeenCalledWith('Line one\nLine two');

🟡 Issues that reduce test isolation or maintainability

4. vi.stubGlobal not cleaned up in most test files

projects.test.js, sessions.test.js, and search.test.js all call vi.stubGlobal('fetch', vi.fn()) in beforeEach but have no afterEach to call vi.unstubAllGlobals(). While Vitest isolates module state per file, stub leakage between files in the same worker can occur in certain configurations. export.test.js correctly uses afterEach(() => vi.restoreAllMocks()) — apply the same pattern everywhere:

afterEach(() => {
    vi.unstubAllGlobals();
    vi.restoreAllMocks();
});

Similarly, app.test.js stubs window.scrollTo and Element.prototype.scrollIntoView in beforeAll and never restores them.

5. export.test.js — In-flight button state is not verified

The bulkExport shows progress then completes on success test description says "shows progress" but only checks the post-completion state:

expect(btn.textContent.trim()).toBe('Export all');
expect(btn.disabled).toBe(false);

The source clearly sets btn.disabled = true; btn.textContent = 'Exporting...' while the request is in flight. Consider verifying this intermediate state using vi.waitFor or by holding the fetch promise before resolving it.

6. sessions.test.js — Fixture ID mismatch

SESSION_DETAIL has session_id: 'sess-1', but the "marks the selected session active" test passes 'sess-2' to showWorkspace. The real loadSession is called with 'sess-2' and the mock returns the sess-1 detail object. The test still passes because it only asserts sidebar markup, not session content, but the inconsistency is misleading:

// Either update SESSION_DETAIL or add a SESSION_DETAIL_2 fixture
// so the loaded content matches the selected session ID.

🟢 Minor / low-severity notes

7. vitest.config.js — Only lines threshold gated

thresholds: {
    lines: 50,
}

Branch and function coverage can be significantly lower than line coverage (dead branches, uncalled code paths). Consider adding:

thresholds: {
    lines: 50,
    functions: 50,
    branches: 40,
}

8. file_read.test.js — Fragile negative assertion

expect(html).not.toContain('lines');

If finishToolResult's wrapper HTML ever gains a class name like "tool-lines", this assertion breaks spuriously. A tighter assertion would be:

expect(html).not.toMatch(/\d+ lines/);

9. search.test.js — Module-level lastSearchRequestId not reset

lastSearchRequestId in search.js is module-level and increments across tests. The stale-response test relies on relative ordering (first ID < second ID), so it works regardless of absolute values, but explicitly resetting it in beforeEach (via a reset export or vi.resetModules()) would make the test intent clearer and more robust if test ordering ever changes.

✅ What is working well

  • The router tests (app.test.js) cover all documented routing branches including cached-vs-cold session loading and malformed URI fallback.
  • The stale-request test in search.test.js correctly verifies the localRequestId guard.
  • The blob fallback test in export.test.js correctly derives the filename ('sess-abcdef12'.slice(0, 8)'session-sess-abc.md'), matching the source.
  • HTML escaping is tested in bash.test.js and edit.test.js.
  • CI enforcement via npm run test:coverage with the 50% threshold in vitest.config.js is well-wired.
  • The Rollup native binary workaround in ci.yml for cross-platform npm optional-dep issues is a solid addition.

projects.test.js missing /api/export/state stub
Invalid — showProjects returns early on empty list (L36–39) and API error (L27–32) before ever calling /api/export/state

Add branch/function thresholds

Skip — aggregate already ~59% branches / ~72% functions; adds CI rigidity with little gain

lastSearchRequestId not reset

Skip — stale-response test uses relative ordering; reset needs vi.resetModules() or prod export

@CodeRabbit. Am I right?

@coderabbitai

coderabbitai Bot commented Jun 16, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
echo "=== projects.js ==="
cat static/js/projects.js

Length of output: 7564



@clean6378-max-it — you are right on all three counts.

1. projects.test.js — missing /api/export/state stub (Issue #2)

After reading the source, showProjects has explicit early returns before ever reaching the /api/export/state call:

  • Non-OK response → return; (around line 32)
  • Empty project list → return; (around line 39)

/api/export/state is only fetched on the happy path when at least one project exists. The empty-state and error tests in projects.test.js never reach that branch, so the inconsistent mocking is fine. My concern was wrong — withdrawing it.

2. Branch/function thresholds (Issue #7)

Your point stands. At ~59% branch and ~72% function coverage already, the tests are well past any meaningful threshold. Adding hard CI gates at this stage would create friction without improving test quality, and would need to be calibrated against future refactors. Reasonable to skip.

3. lastSearchRequestId not reset (Issue #9)

The stale-response test only depends on requestId_A < requestId_B (relative ordering), so any starting value works. A proper reset would need vi.resetModules() which re-initialises all module-level state — heavyweight and potentially disruptive to other tests in the same file. Reasonable to skip.

(¬‿¬)🐇

Mock showConfirm in export tests, verify in-flight export button state,
key session fetch mocks by session ID, tighten file_read assertion,
add global stub cleanup across test files, and await clipboard write
assertion defensively.
Comment thread .github/workflows/ci.yml
Comment thread vitest.config.js
Comment thread static/js/export.test.js Outdated
Comment thread static/js/sessions.test.js Outdated
Comment thread static/js/render/tool_use/edit.test.js
Raise vitest thresholds to lines 80 / functions 70 / branches 50 so
the ~85% gain is actually protected. Stub URL blob methods without
replacing the constructor, restore navigator.clipboard after copyAll
tests, assert new_string HTML escaping in edit.test.js, and document
that CI test:coverage runs the full vitest suite.
@clean6378-max-it

Copy link
Copy Markdown
Collaborator Author

@CodeRabbit, all @timon0305 's comments are addressed by my last commit?

@coderabbitai

coderabbitai Bot commented Jun 16, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Get all review comments on the PR
gh pr view 86 --repo cppalliance/claude-code-chat-browser --json reviews,comments

echo ""
echo "=== PR Review Comments ==="
gh api /repos/cppalliance/claude-code-chat-browser/pulls/86/comments \
  --jq '.[] | select(.user.login == "timon0305") | {id: .id, path: .path, body: .body, line: .line}'

echo ""
echo "=== Issue Comments from timon0305 ==="
gh api /repos/cppalliance/claude-code-chat-browser/issues/86/comments \
  --jq '.[] | select(.user.login == "timon0305") | {id: .id, body: .body}'

echo ""
echo "=== Latest commit on PR ==="
gh pr view 86 --repo cppalliance/claude-code-chat-browser --json commits \
  --jq '.commits[-1]'

Length of output: 64487


🏁 Script executed:

#!/bin/bash
echo "=== vitest.config.js ==="
cat vitest.config.js

echo ""
echo "=== export.test.js (URL stub area) ==="
grep -n "URL\|createObjectURL\|revokeObjectURL\|spyOn" static/js/export.test.js

echo ""
echo "=== sessions.test.js (clipboard area) ==="
grep -n "clipboard\|afterEach\|unstubAllGlobals\|restoreAllMocks" static/js/sessions.test.js

echo ""
echo "=== edit.test.js ==="
cat static/js/render/tool_use/edit.test.js

echo ""
echo "=== ci.yml (test:coverage area) ==="
grep -n "test:coverage\|npm test\|npm run" .github/workflows/ci.yml

Length of output: 3794



@clean6378-max-it — yes, all five of @timon0305's inline comments are addressed by the last commit. Here's a quick verification:

# File Comment Status
1 ci.yml Confirm test:coverage runs the same suite as npm test ✅ A clarifying comment was added directly above the step
2 vitest.config.js Raise threshold closer to achieved number; add functions/branches gates ✅ Now lines: 80, functions: 70, branches: 50
3 export.test.js Don't replace the whole URL global — stub only createObjectURL/revokeObjectURL ✅ Constructor is untouched; only the two static methods are saved, patched, and restored in a finally block (lines 115–143) — equivalent protection to vi.spyOn
4 sessions.test.js Restore navigator.clipboard in afterEach clipboardRestore variable captured, restored in afterEach alongside vi.unstubAllGlobals()
5 edit.test.js Assert new_string (<worse>) is escaped, not just old_string ✅ Both not.toContain('<worse>') and toContain('&lt;worse&gt;') are now present

All five concerns are resolved. The only minor note on #3 is that the implementation uses manual save/restore rather than vi.spyOn, but the outcome — URL constructor stays intact, both methods are properly cleaned up — is identical.

@clean6378-max-it clean6378-max-it requested a review from wpak-ai June 16, 2026 16:35
@wpak-ai wpak-ai merged commit 0fe1d66 into master Jun 16, 2026
16 checks passed
@wpak-ai wpak-ai deleted the test/frontend-render-routing-coverage branch June 16, 2026 18:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

claude-code-chat-browser: Frontend rendering/routing test coverage

3 participants