useBountyStatus.test.tsx tests a hook that no longer exists: it mocks fetchBounty, asserts data with enabled:false, and leaks the module-level poller registry between cases

Open
#846 1 comment 0 reactions 0 assignees View on GitHub

Maintainers usually reply within 1 day

Nobody has claimed this yet.

Assessment

Difficulty
3/5
Estimated time
1-2 days
Newbie friendliness
57/100
Issue type
Bug
Clarity
Clearly specified
Activity status
Active
Tech stack
react, typescript
Domain
frontend, testing

Research direction

Start with src/hooks/useBountyStatus.test.tsx, then read src/hooks/useBountyStatus.ts and src/hooks/bountyPoller.ts to align tests with the hook’s subscription registry. Run npx jest src/hooks/useBountyStatus.test.tsx to confirm the failures; done means the suite isolates registry state and covers the stated subscription, polling, refetch, and failed-fetch behavior. The issue also raises whether to remove or reconnect useSmartPolling, which adds scope.

Written by the indexing model from the issue text.

Description

bug testing

Location

src/hooks/useBountyStatus.test.tsx (whole file), against the current src/hooks/useBountyStatus.ts and src/hooks/bountyPoller.ts.

Problem

The suite was written for the pre-#631 hook, which ran its own useSmartPolling loop over an injected fetchBounty. Its own header still says so: "Covers the hook wrapping useSmartPolling". The hook is now a thin useSyncExternalStore over the module-level registry in bountyPoller.ts. The tests were never updated, and several of them cannot pass against the current implementation:

  1. enabled: false cases assert fetched data. Cases 1 and 2 render with enabled: false. In the current hook that makes the subscribe function a no-op (useBountyStatus.ts:42), so subscribeBounty is never called, no entry is created, and getBountySnapshot returns { bounty: null, source: null, loading: false, error: null }. The assertions expect(result.current.bounty).toEqual(mockBounty) and expect(result.current.source).toBe("live") are asserting state that the code has no way to produce.

  2. Case 3 asserts a callback the hook never invokes. onStatusChange is destructured and then never referenced anywhere in useBountyStatus.ts (filed separately), so expect(onStatusChange).toHaveBeenCalledWith("open") can only time out.

  3. Cross-test state leakage. registry in bountyPoller.ts is module-level and is never reset. beforeEach(() => jest.clearAllMocks()) clears the fetchBounty mock but not the registry, so an entry created by one case — along with its bounty, its source and, if the terminal-status branch did not fire, its live setInterval — survives into the next. Cases therefore pass or fail depending on declaration order, and the real intervals keep firing across the file.

  4. The mock is now at the wrong seam. jest.mock("@/lib/api", () => ({ fetchBounty: jest.fn() })) replaces the module the registry calls, not anything the hook calls. That is why the suite looks like it is exercising fetch behaviour while actually exercising nothing: the hook under test makes no API call of its own, and the mock also strips every other export of @/lib/api, so any future import from that module inside the hook silently becomes undefined.

Relatedly, useSmartPolling is now dead code: the only references to it anywhere under src/ outside its own file are its own test (src/hooks/useSmartPolling.test.tsx) and a comment in useClaimRace.ts. Nothing renders it. The open report #568 ("useSmartPolling stops polling after the first re-render because useBountyStatus passes inline fetchFn/compareFn/onDataChange") describes a call site that no longer exists, so that issue is unverifiable as written and should be re-scoped or closed.

Why it matters

This is worse than having no tests, which is the state #620 was filed about. The file's presence is what makes useBountyStatus look covered, so the genuine defects now living in bountyPoller.ts (a stopped poller that never restarts, a failed fetch blanking the bounty, refetch being a no-op) sit behind a green-looking test file that exercises none of them. And because the suite cannot pass as written, CI is either red on this file or the file is being skipped — both of which hide the same thing.

Reproduction

npx jest src/hooks/useBountyStatus.test.tsx — cases 1, 2 and 3 fail for the reasons above (1 and 2 on the bounty/source assertion, 3 on the waitFor timeout).

Suggested fix

Rewrite the suite against the seam the hook actually has:

  • Export a __resetBountyPollerRegistry() (test-only) from bountyPoller.ts, or clear the map via jest.resetModules() in beforeEach, so each case starts clean.
  • Mock @/lib/api with ...jest.requireActual("@/lib/api") spread in, overriding only fetchBounty, so the rest of the module stays intact.
  • Use fake timers and assert the behaviours that matter: one shared fetch loop for two subscribers on the same id; polling stops on a terminal status; polling restarts when a new subscriber arrives after the last one left; refetch() performs a fetch while the loop is stopped; a failed fetch surfaces error without blanking a previously loaded bounty.
  • Drop the enabled: false data assertions, and replace them with the real contract: enabled: false means no subscription and no fetch.

Then decide useSmartPolling's fate: delete the hook and its test if the registry is the way forward, or re-wire it and keep its tests meaningful. Leaving it in src/hooks/ invites a contributor to adopt it and inherit the bugs reported against it.

Dominant language
TypeScript
Stars
1
Forks
94
Avg merge
1h 2m
Merged PRs (30d)
25

Getting set up

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from MergeFi/frontend

All issues in MergeFi/frontend

Similar issues

More TypeScript issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.