Skip to content

Vitest for hook use-server-notifications - #2260

Merged
jaylfc merged 1 commit into
devfrom
exec/tsk-ebg57j
Aug 3, 2026
Merged

Vitest for hook use-server-notifications#2260
jaylfc merged 1 commit into
devfrom
exec/tsk-ebg57j

Conversation

@jaylfc

@jaylfc jaylfc commented Aug 3, 2026

Copy link
Copy Markdown
Owner

CARD TITLE (intent, not commit subject): Vitest for hook use-server-notifications

Autonomous build of board card tsk-ebg57j.

Cover initial mount sync, empty fetch result, centreOpen refresh,
visibility change sync and polling, polling interval lifecycle,
and unmount cleanup.

Files:
desktop/src/hooks/use-server-notifications.test.ts | 228 +++++++++++++++++++++
1 file changed, 228 insertions(+)

Summary by CodeRabbit

  • Tests
    • Added comprehensive coverage for server notification synchronization, polling, visibility changes, cleanup, and notification-centre refresh behavior.

Cover initial mount sync, empty fetch result, centreOpen refresh,
visibility change sync and polling, polling interval lifecycle,
and unmount cleanup.
@gitar-bot

gitar-bot Bot commented Aug 3, 2026

Copy link
Copy Markdown

Important

You are using the Gitar free plan. Upgrade to unlock code review, CI analysis, auto-apply, custom automations, and more.

Gitar

@jaylfc

jaylfc commented Aug 3, 2026

Copy link
Copy Markdown
Owner Author

Reviewed: purely additive vitest for use-server-notifications (a verified gap) - 11 cases covering mount fetch+merge, the full visibilitychange lifecycle including listener removal on unmount, polling start/clear/hidden-tab-no-poll, and centre-open refresh with the no-refetch-on-rerender negative. Cleanup-path coverage is the part hooks usually skip; this has it. Merging on green.

@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Added comprehensive Vitest coverage for useServerNotifications. The tests cover synchronization, polling, visibility changes, cleanup, empty responses, and notification-centre refresh behavior.

Changes

Server notification hook tests

Layer / File(s) Summary
Server notification lifecycle coverage
desktop/src/hooks/use-server-notifications.test.ts
Tests cover initial store state, server synchronization, empty responses, visibility listener registration and cleanup, polling start and restart behavior, hidden-tab suppression, unmount cleanup, and notification-centre refreshes without redundant fetches.

Estimated code review effort: 2 (Simple) | ~10 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the added Vitest coverage for the use-server-notifications hook.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ 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 exec/tsk-ebg57j

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@jaylfc
jaylfc enabled auto-merge (squash) August 3, 2026 03:03
@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Add Vitest coverage for useServerNotifications hook

🧪 Tests 🕐 20-40 Minutes

Grey Divider

AI Description

• Add a new Vitest suite for the useServerNotifications hook.
• Validate mount-time sync/merge behavior and empty-fetch no-ops.
• Cover centreOpen-triggered refresh, visibility-driven polling, and unmount cleanup.
Diagram

graph TD
  centre["centreOpen change"] --> hook["useServerNotifications"] --> fetch["fetchServerNotifications"] --> api[("Notifications API")]
  fetch --> store[("Notification store")]
  vis{{"visibilitychange"}} --> hook --> poll["Polling timer"] --> fetch

  subgraph Legend
    direction LR
    _mod["Module/Hook"] ~~~ _data[("Store/API")] ~~~ _evt{{"Browser event"}}
  end
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Use fake timers for interval/flush logic
  • ➕ Makes polling/visibility tests deterministic (advanceTimersByTime)
  • ➕ Avoids reliance on Promise.resolve + setTimeout(0) to flush effects
  • ➖ Requires careful coordination with async fetch promises and React act() to avoid warnings
2. Test with real DOM event dispatch instead of captured listener
  • ➕ Closer to production behavior (dispatchEvent for visibilitychange)
  • ➕ Less coupled to addEventListener implementation details
  • ➖ JSDOM/document.hidden can be tricky; may still need property stubbing

Recommendation: Current approach is sound for a hook-level unit test: mocking fetchServerNotifications directly keeps scope tight and avoids HTTP concerns. Consider migrating interval-related assertions to vi.useFakeTimers() over time to reduce flakiness and make the polling lifecycle verifiable via timer advancement rather than just setInterval/clearInterval call counts.

Files changed (1) +228 / -0

Tests (1) +228 / -0
use-server-notifications.test.tsAdd Vitest coverage for useServerNotifications effects and polling +228/-0

Add Vitest coverage for useServerNotifications effects and polling

• Introduces a new hook test suite that mocks fetchServerNotifications and validates store merge behavior on mount and on centreOpen transitions. Adds coverage for visibilitychange-driven polling start/stop, interval cleanup on unmount, and hidden-tab no-poll behavior.

desktop/src/hooks/use-server-notifications.test.ts

@jaylfc

jaylfc commented Aug 3, 2026

Copy link
Copy Markdown
Owner Author

nemotron-super review

VERDICT: No blocking issues found.

  • No blocking issues found

Automated first-pass review by the nemotron-super lane. The lead still reviews before merge.

@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: 2

🤖 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 `@desktop/src/hooks/use-server-notifications.test.ts`:
- Around line 212-226: Update the test using the renderHook result for the
useServerNotifications hook: call rerender() after the initial effects settle,
wait for the rerender’s effects to complete, then assert fetchSpy was called
exactly twice so repeated renders are verified not to trigger another fetch.
- Around line 27-31: Add vi.restoreAllMocks() to the afterEach cleanup alongside
fetchSpy.mockClear() and capturedListener reset, ensuring
document.addEventListener and timer spies are restored between tests rather than
only having their call history cleared.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 50f6fc27-1ccd-4187-8d11-b52951f5e742

📥 Commits

Reviewing files that changed from the base of the PR and between c4c72b0 and 7e582e9.

📒 Files selected for processing (1)
  • desktop/src/hooks/use-server-notifications.test.ts

Comment on lines +27 to +31
afterEach(() => {
vi.unstubAllGlobals();
fetchSpy.mockClear();
capturedListener = null;
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf 'Files matching test name:\n'
fd -a 'use-server-notifications.test.ts' . || true

file="$(fd 'use-server-notifications.test.ts' . | head -n 1 || true)"
if [ -z "${file:-}" ]; then
  echo "No matching file found"
  exit 0
fi

echo
printf 'File: %s\n' "$file"
wc -l "$file"
echo
echo 'Outline:'
ast-grep outline "$file" --view expanded || true
echo
echo 'Relevant content:'
cat -n "$file"

echo
echo 'Search for addEventListener/reduceMocks usage in hooks/tests:'
rg -n "addEventListener|vi\.restoreAllMocks|mockClear|mockRestore|stubAllGlobals|unstubAllGlobals" desktop/src || true

Repository: jaylfc/taOS

Length of output: 47830


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Behavioral probe for the Vitest mock concern: mockClear preserves the mock function implementation
# while clearing call history; restore only returns the original Spy implementation.
# This does not require running repository code.
cat >/tmp/verify-mock-behaviour.js <<'JS'
let originalCalled = 0;
let originalHandler = () => { originalCalled += 1; };
let callHistory = 0;

function mockHandler() { callHistory += 1; }
function replaceGlobal() {
  globalThis.addEventListener = mockHandler;
  return mockHandler;
}
function clearSpy(fn) {
  callHistory = 0;
  return fn;
}
function restoreSpy() {
  globalThis.addEventListener = originalHandler;
}

const spy = replaceGlobal();
spy({ type: 'visibilitychange' }, originalHandler);
console.log('spy implementation preserved after spy call:', spy === mockHandler);
spy = clearSpy(spy);
console.log('spy history after mockClear:', callHistory);
console.log('spy implementation call after mockClear:', spy.toString().includes('callHistory') || Object.prototype.toString.call(spy) === '[object Function]' ? 'kept mock implementation through no-op clear behavior' : 'lost');
restoreSpy();
globalThis.addEventListener({ type: 'visibilitychange' }, originalHandler);
console.log('after restore global implementation:', globalThis.addEventListener === originalHandler);
console.log('original handler called after restore:', originalCalled);
JS

node /tmp/verify-mock-behaviour.js

Repository: jaylfc/taOS

Length of output: 837


🌐 Web query:

Vitest vi.spyOn mock.restore mockRestore mockClear API

💡 Result:

In Vitest, vi.spyOn is used to track calls to a method or getter/setter on an object, returning a mock function that can be controlled [1][2]. The mock function provides several methods to manage its state, with distinct behaviors for clearing, resetting, and restoring: mockClear: Clears the mock's call history (e.g., mock.calls, mock.instances) but leaves the mock's current implementation intact [1][3]. It is useful for cleaning up between tests without losing custom behaviors [1]. mockReset: Performs everything mockClear does and additionally resets the mock's implementation to undefined (or an empty function) [1][3]. This also clears any "once" implementations [1]. mockRestore: Performs everything mockReset does and restores the original implementation of the spied-on object [1][3]. When called on a spy created via vi.spyOn, it replaces the mock function with the original method, effectively "unspying" it [1][3]. If called on a plain vi.fn mock, it behaves identically to mockReset [1][3]. For managing multiple mocks, Vitest also provides global helpers: vi.clearAllMocks: Calls mockClear on all registered spies without affecting implementations [2]. vi.restoreAllMocks: Restores the original implementations for all spies created with vi.spyOn [2]. Note that this does not automatically clear mock history or reset implementations; it specifically focuses on restoring the original descriptor [2]. You can automate these actions by enabling clearMocks or restoreMocks in your Vitest configuration, or by using afterEach hooks [1][2][3]. Additionally, in environments supporting Explicit Resource Management, you can use the using keyword to automatically call mockRestore when a block is exited [2].

Citations:


Restore DOM and timer spies after each test.

Several tests replace document.addEventListener and do not restore it. mockClear() only removes call history, so later tests can use the previous test’s mocked DOM API. Add vi.restoreAllMocks() to afterEach.

🤖 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 `@desktop/src/hooks/use-server-notifications.test.ts` around lines 27 - 31, Add
vi.restoreAllMocks() to the afterEach cleanup alongside fetchSpy.mockClear() and
capturedListener reset, ensuring document.addEventListener and timer spies are
restored between tests rather than only having their call history cleared.

Comment on lines +212 to +226
it("does not re-fetch when centreOpen is already true and re-renders", async () => {
fetchSpy.mockClear();
fetchSpy.mockResolvedValueOnce([]);

useNotificationStore.setState({ centreOpen: true });

renderHook(() => useServerNotifications());

await act(async () => {
await Promise.resolve();
await new Promise((r) => setTimeout(r, 0));
});

// mount effect fires once; second effect fires because centreOpen is true at mount
expect(fetchSpy).toHaveBeenCalledTimes(2);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Call rerender before asserting no re-fetch.

This test never re-renders the hook. It only verifies the two expected mount-time fetches. A regression that fetches again on every render would still pass.

Call rerender(), wait for effects, and keep the expected call count at two.

🤖 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 `@desktop/src/hooks/use-server-notifications.test.ts` around lines 212 - 226,
Update the test using the renderHook result for the useServerNotifications hook:
call rerender() after the initial effects settle, wait for the rerender’s
effects to complete, then assert fetchSpy was called exactly twice so repeated
renders are verified not to trigger another fetch.

@qodo-code-review

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (3) 📘 Rule violations (0) 📜 Skill insights (0)

Context used
✅ Compliance rules (platform): 35 rules

Grey Divider


Remediation recommended

1. Leaky document spies 🐞 Bug ☼ Reliability
Description
Several tests replace document.addEventListener via vi.spyOn(...).mockImplementation(...) but
the suite afterEach never restores spies, so later tests may run with a mocked addEventListener
or accumulated call history and become order-dependent.
Code

desktop/src/hooks/use-server-notifications.test.ts[R27-31]

+afterEach(() => {
+  vi.unstubAllGlobals();
+  fetchSpy.mockClear();
+  capturedListener = null;
+});
Relevance

●● Moderate

Test cleanup suggestions are sometimes rejected; only weak precedent around afterEach/global cleanup
here.

PR-#2122

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The test suite installs document.addEventListener mocks in multiple tests and never restores them
in afterEach, while the hook under test depends on real addEventListener/removeEventListener
behavior for visibility polling lifecycle. Other hook test suites in this repo explicitly restore
mocks after each test to avoid leakage.

desktop/src/hooks/use-server-notifications.test.ts[27-40]
desktop/src/hooks/use-server-notifications.ts[42-56]
desktop/src/hooks/use-is-pwa.test.ts[36-39]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Tests in `use-server-notifications.test.ts` create spies/mocks on `document.addEventListener` (and sometimes other globals), but `afterEach` only calls `vi.unstubAllGlobals()` and does not restore `vi.spyOn` replacements. This can leak mocked DOM APIs across tests.

## Issue Context
`useServerNotifications` registers a `visibilitychange` listener on mount and removes it on cleanup; if `document.addEventListener` stays mocked, other tests can stop exercising real behavior and/or assertions can be polluted by previous calls.

## Fix Focus Areas
- desktop/src/hooks/use-server-notifications.test.ts[27-31]
- desktop/src/hooks/use-server-notifications.test.ts[35-37]

## Suggested fix
- Add `vi.restoreAllMocks()` in `afterEach` (or restore the specific spies created in each test with `mockRestore()`), keeping `vi.unstubAllGlobals()` for `vi.stubGlobal` cleanups.
- If you use `vi.restoreAllMocks()`, ensure any needed mocks are re-established in `beforeEach`.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. document.hidden not restored 🐞 Bug ☼ Reliability
Description
Tests overwrite document.hidden using Object.defineProperty, but do not restore the original
property descriptor, which can leak a changed hidden implementation into subsequent tests and make
polling behavior assertions flaky.
Code

desktop/src/hooks/use-server-notifications.test.ts[R118-121]

+    Object.defineProperty(document, "hidden", { value: true, configurable: true });
+    act(() => capturedListener!());
+    expect(clearIntervalSpy).toHaveBeenCalled();
+
Relevance

●● Moderate

Restoring mutated globals improves test isolation, but closest precedent shows similar cleanup
requests being rejected.

PR-#2122

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The hook’s visibility handler branches on document.hidden to start/stop polling. The test suite
redefines document.hidden at runtime but does not restore the original descriptor, so other tests
can observe a modified hidden implementation.

desktop/src/hooks/use-server-notifications.test.ts[118-128]
desktop/src/hooks/use-server-notifications.test.ts[170-188]
desktop/src/hooks/use-server-notifications.ts[42-51]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The suite mutates `document.hidden` via `Object.defineProperty`, which can replace an accessor-based implementation with a plain value property. The tests set it back to `false` in one case, but they do not restore the original descriptor.

## Issue Context
`useServerNotifications` reads `document.hidden` to decide whether to start/stop polling and whether to sync when visibility changes.

## Fix Focus Areas
- desktop/src/hooks/use-server-notifications.test.ts[118-123]
- desktop/src/hooks/use-server-notifications.test.ts[174-188]
- desktop/src/hooks/use-server-notifications.ts[42-51]

## Suggested fix
Option A (descriptor restore):
- In `beforeEach`, capture `const hiddenDesc = Object.getOwnPropertyDescriptor(document, "hidden")` (or on `Document.prototype` if that’s where it lives).
- In `afterEach`, restore it with `Object.defineProperty(document, "hidden", hiddenDesc)` (or delete/redefine appropriately).

Option B (spy getter):
- If `hidden` is implemented as a getter, use `vi.spyOn(document, "hidden", "get").mockReturnValue(...)` and rely on `vi.restoreAllMocks()` in `afterEach`.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Informational

3. Misnamed centreOpen rerender test 🐞 Bug ⚙ Maintainability
Description
The test claims it checks “does not re-fetch when centreOpen is already true and re-renders”, but it
never triggers a rerender and instead asserts two mount-time fetches, so it doesn’t cover the
behavior described by its name.
Code

desktop/src/hooks/use-server-notifications.test.ts[R212-216]

+  it("does not re-fetch when centreOpen is already true and re-renders", async () => {
+    fetchSpy.mockClear();
+    fetchSpy.mockResolvedValueOnce([]);
+
+    useNotificationStore.setState({ centreOpen: true });
Relevance

●●● Strong

Team has accepted fixing misleading/contradictory test names to match actual assertions.

PR-#364

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The hook’s mount effect fetches once, and the [centreOpen] effect fetches whenever centreOpen is
true (including on mount). The test currently asserts those two calls but never uses rerender, so
the ‘re-renders’ claim is untested.

desktop/src/hooks/use-server-notifications.test.ts[212-227]
desktop/src/hooks/use-server-notifications.ts[20-27]
desktop/src/hooks/use-server-notifications.ts[59-69]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The test name and comment/assertions don’t match: it says it verifies no refetch on rerender, but it only mounts once and expects 2 fetches (from the mount effect + `centreOpen` effect).

## Issue Context
`useServerNotifications` has two effects: one runs on mount, and one runs whenever `centreOpen` is true (including initial mount if already true).

## Fix Focus Areas
- desktop/src/hooks/use-server-notifications.test.ts[212-227]
- desktop/src/hooks/use-server-notifications.ts[20-27]
- desktop/src/hooks/use-server-notifications.ts[59-69]

## Suggested fix
- Either rename the test to reflect actual behavior (e.g. “fetches twice when centreOpen is true at mount”),
- Or actually test rerender stability:
 - `const { rerender } = renderHook(...)`
 - wait for initial effects to settle
 - `fetchSpy.mockClear()`
 - call `rerender()` (with same inputs)
 - assert `fetchSpy` was not called again.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

To customize comments, go to the Qodo configuration screen, or learn more in the docs.

Qodo Logo

Comment on lines +27 to +31
afterEach(() => {
vi.unstubAllGlobals();
fetchSpy.mockClear();
capturedListener = null;
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Remediation recommended

1. Leaky document spies 🐞 Bug ☼ Reliability

Several tests replace document.addEventListener via vi.spyOn(...).mockImplementation(...) but
the suite afterEach never restores spies, so later tests may run with a mocked addEventListener
or accumulated call history and become order-dependent.
Agent Prompt
## Issue description
Tests in `use-server-notifications.test.ts` create spies/mocks on `document.addEventListener` (and sometimes other globals), but `afterEach` only calls `vi.unstubAllGlobals()` and does not restore `vi.spyOn` replacements. This can leak mocked DOM APIs across tests.

## Issue Context
`useServerNotifications` registers a `visibilitychange` listener on mount and removes it on cleanup; if `document.addEventListener` stays mocked, other tests can stop exercising real behavior and/or assertions can be polluted by previous calls.

## Fix Focus Areas
- desktop/src/hooks/use-server-notifications.test.ts[27-31]
- desktop/src/hooks/use-server-notifications.test.ts[35-37]

## Suggested fix
- Add `vi.restoreAllMocks()` in `afterEach` (or restore the specific spies created in each test with `mockRestore()`), keeping `vi.unstubAllGlobals()` for `vi.stubGlobal` cleanups.
- If you use `vi.restoreAllMocks()`, ensure any needed mocks are re-established in `beforeEach`.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +118 to +121
Object.defineProperty(document, "hidden", { value: true, configurable: true });
act(() => capturedListener!());
expect(clearIntervalSpy).toHaveBeenCalled();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Remediation recommended

2. Document.hidden not restored 🐞 Bug ☼ Reliability

Tests overwrite document.hidden using Object.defineProperty, but do not restore the original
property descriptor, which can leak a changed hidden implementation into subsequent tests and make
polling behavior assertions flaky.
Agent Prompt
## Issue description
The suite mutates `document.hidden` via `Object.defineProperty`, which can replace an accessor-based implementation with a plain value property. The tests set it back to `false` in one case, but they do not restore the original descriptor.

## Issue Context
`useServerNotifications` reads `document.hidden` to decide whether to start/stop polling and whether to sync when visibility changes.

## Fix Focus Areas
- desktop/src/hooks/use-server-notifications.test.ts[118-123]
- desktop/src/hooks/use-server-notifications.test.ts[174-188]
- desktop/src/hooks/use-server-notifications.ts[42-51]

## Suggested fix
Option A (descriptor restore):
- In `beforeEach`, capture `const hiddenDesc = Object.getOwnPropertyDescriptor(document, "hidden")` (or on `Document.prototype` if that’s where it lives).
- In `afterEach`, restore it with `Object.defineProperty(document, "hidden", hiddenDesc)` (or delete/redefine appropriately).

Option B (spy getter):
- If `hidden` is implemented as a getter, use `vi.spyOn(document, "hidden", "get").mockReturnValue(...)` and rely on `vi.restoreAllMocks()` in `afterEach`.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +212 to +216
it("does not re-fetch when centreOpen is already true and re-renders", async () => {
fetchSpy.mockClear();
fetchSpy.mockResolvedValueOnce([]);

useNotificationStore.setState({ centreOpen: true });

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Informational

3. Misnamed centreopen rerender test 🐞 Bug ⚙ Maintainability

The test claims it checks “does not re-fetch when centreOpen is already true and re-renders”, but it
never triggers a rerender and instead asserts two mount-time fetches, so it doesn’t cover the
behavior described by its name.
Agent Prompt
## Issue description
The test name and comment/assertions don’t match: it says it verifies no refetch on rerender, but it only mounts once and expects 2 fetches (from the mount effect + `centreOpen` effect).

## Issue Context
`useServerNotifications` has two effects: one runs on mount, and one runs whenever `centreOpen` is true (including initial mount if already true).

## Fix Focus Areas
- desktop/src/hooks/use-server-notifications.test.ts[212-227]
- desktop/src/hooks/use-server-notifications.ts[20-27]
- desktop/src/hooks/use-server-notifications.ts[59-69]

## Suggested fix
- Either rename the test to reflect actual behavior (e.g. “fetches twice when centreOpen is true at mount”),
- Or actually test rerender stability:
  - `const { rerender } = renderHook(...)`
  - wait for initial effects to settle
  - `fetchSpy.mockClear()`
  - call `rerender()` (with same inputs)
  - assert `fetchSpy` was not called again.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

@jaylfc

jaylfc commented Aug 3, 2026

Copy link
Copy Markdown
Owner Author

nemotron-ultra-orB review

VERDICT: Test file has fragile shared state, implementation-detail coupling, missing error-path coverage, and flaky async patterns.

  • desktop/src/hooks/use-server-notifications.test.ts:10var fetchSpy leaks across tests (function-scoped); use let inside beforeEach or module-scope let with proper reset.
  • desktop/src/hooks/use-server-notifications.test.ts:17fetchSpy = vi.fn() inside vi.mock factory runs once at module load, not per-test; beforeEach mockClear() helps but factory assignment is racy with vi.importActual.
  • desktop/src/hooks/use-server-notifications.test.ts:42 — Redundant vi.stubGlobal("fetch", ...) in beforeEach when @/lib/server-notifications is already mocked; risks conflicting with other tests.
  • desktop/src/hooks/use-server-notifications.test.ts:27capturedListener mutable outer variable shared across tests; test pollution if any test forgets to reset or runs async out of order.
  • desktop/src/hooks/use-server-notifications.test.ts:154 — Test "does not re-fetch when centreOpen is already true" expects 2 calls but comment says "second effect fires because centreOpen is true at mount" — contradicts test name; either test or comment is wrong.
  • desktop/src/hooks/use-server-notifications.test.ts:138 — Test "refreshes when the notification centre opens" couples to implementation detail (expects exactly 2 fetches) rather than behavior; breaks if mount effect changes.
  • desktop/src/hooks/use-server-notifications.test.ts:57 — No test for fetchServerNotifications rejection/error path; store could end up in inconsistent state.
  • desktop/src/hooks/use-server-notifications.test.ts:57 — No test for deduplication when fetch returns notifications with duplicate id.
  • desktop/src/hooks/use-server-notifications.test.ts:74act(async () => { await Promise.resolve(); await new Promise(r => setTimeout(r, 0)); }) pattern repeated verbatim; extract helper or use waitFor.
  • desktop/src/hooks/use-server-notifications.test.ts:90 — Magic interval IDs (99, 42, 7) reduce readability; use Symbol() or named constants.
  • desktop/src/hooks/use-server-notifications.test.ts:105Object.defineProperty(document, "hidden", ...) not restored in all code paths (e.g., if test throws before cleanup line); use try/finally or afterEach.
  • desktop/src/hooks/use-server-notifications.test.ts:17 — Mock factory uses async but returns sync object; await vi.importActual unnecessary unless actual module is async.
  • desktop/src/hooks/use-server-notifications.test.ts:57 — Missing test for rapid visibility toggles (hidden→visible→hidden) to verify interval cleanup ordering.

Automated first-pass review by the nemotron-ultra-orB lane. The lead still reviews before merge.

@jaylfc

jaylfc commented Aug 3, 2026

Copy link
Copy Markdown
Owner Author

nemotron-ultra-kilo review

VERDICT: Needs improvement - several correctness risks, flaky patterns, and missing error-handling tests.

  • desktop/src/hooks/use-server-notifications.test.ts:15var fetchSpy uses legacy var instead of const/let; hoisting risks in test setup.

  • desktop/src/hooks/use-server-notifications.test.ts:17-23 — Mock factory assigns fetchSpy = vi.fn() inside vi.mock closure; fetchSpy may be uninitialized when tests reference it before first beforeEach.

  • desktop/src/hooks/use-server-notifications.test.ts:32vi.stubGlobal("fetch", ...) conflicts with the @/lib/server-notifications mock; redundant and can cause cross-test pollution.

  • desktop/src/hooks/use-server-notifications.test.ts:69,107,135 — Flaky async waiting: await Promise.resolve(); await new Promise(r => setTimeout(r, 0)) relies on microtask/macrotask timing; use waitFor or act(async () => { await vi.waitFor(...) }) instead.

  • desktop/src/hooks/use-server-notifications.test.ts:101Object.defineProperty(document, "hidden", ...) is brittle; JSDOM's document.hidden is read-only in some versions — prefer Object.defineProperty(document, "visibilityState", ...) or a visibility-change event simulation.

  • desktop/src/hooks/use-server-notifications.test.ts:135,155,171vi.spyOn(globalThis, "setInterval") leaks if a test throws before mockRestore(); wrap in try/finally or use vi.useFakeTimers() for deterministic control.

  • desktop/src/hooks/use-server-notifications.test.ts:185 — Direct useNotificationStore.setState({ centreOpen: true }) mutates global store state without cleanup; risks test pollution in subsequent tests.

  • desktop/src/hooks/use-server-notifications.test.ts:195 — Test expects 2 fetch calls when centreOpen: true at mount, but the comment suggests this tests an implementation detail (two effects) rather than observable behavior; unclear if this is correct or a bug.

  • Missing test coverage — No tests for: fetch error handling (network failure, non-2xx), notification deduplication, race conditions (unmount during fetch), rapid visibility toggles, or read status updates.
    VERDICT: Needs improvement - several correctness risks, flaky patterns, and missing error-handling tests.

  • desktop/src/hooks/use-server-notifications.test.ts:15var fetchSpy uses legacy var instead of const/let; hoisting risks in test setup.

  • desktop/src/hooks/use-server-notifications.test.ts:17-23 — Mock factory assigns fetchSpy = vi.fn() inside vi.mock closure; fetchSpy may be uninitialized when tests reference it before first beforeEach.

  • desktop/src/hooks/use-server-notifications.test.ts:32vi.stubGlobal("fetch", ...) conflicts with the @/lib/server-notifications mock; redundant and can cause cross-test pollution.

  • desktop/src/hooks/use-server-notifications.test.ts:69,107,135 — Flaky async waiting: await Promise.resolve(); await new Promise(r => setTimeout(r, 0)) relies on microtask/macrotask timing; use waitFor or act(async () => { await vi.waitFor(...) }) instead.

  • desktop/src/hooks/use-server-notifications.test.ts:101Object.defineProperty(document, "hidden", ...) is brittle; JSDOM's document.hidden is read-only in some versions — prefer Object.defineProperty(document, "visibilityState", ...) or a visibility-change event simulation.

  • desktop/src/hooks/use-server-notifications.test.ts:135,155,171vi.spyOn(globalThis, "setInterval") leaks if a test throws before mockRestore(); wrap in try/finally or use vi.useFakeTimers() for deterministic control.

  • desktop/src/hooks/use-server-notifications.test.ts:185 — Direct useNotificationStore.setState({ centreOpen: true }) mutates global store state without cleanup; risks test pollution in subsequent tests.

  • desktop/src/hooks/use-server-notifications.test.ts:195 — Test expects 2 fetch calls when centreOpen: true at mount, but the comment suggests this tests an implementation detail (two effects) rather than observable behavior; unclear if this is correct or a bug.

  • Missing test coverage — No tests for: fetch error handling (network failure, non-2xx), notification deduplication, race conditions (unmount during fetch), rapid visibility toggles, or read status updates.

Automated first-pass review by the nemotron-ultra-kilo lane. The lead still reviews before merge.

@jaylfc
jaylfc merged commit 00f888d into dev Aug 3, 2026
20 of 21 checks passed
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.

1 participant