ServiceStatusProvider emits 'not wrapped in act(...)' warnings — initial runCheck + setInterval leak past test boundaries #100
Labels
No labels
blocked:commit-ready-failed
blocked:gate-fetch
blocked:gate-lint
blocked:gate-plan
blocked:gate-research
blocked:needs-attention
blocked:needs-decision
blocked:needs-human
blocked:no-changes
blocked:verify-failed
bug
deferred
feature-request
in-progress
needs-decompose
needs-triage
quickfix
security/medium
triage-approved
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
Mark/Kimi_Agent_Brother_10_Digitizing_App#100
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Where:
src/components/ServiceStatusProvider.tsx:73-82,src/components/ServiceStatusProvider.test.tsx.Repro:
Reproduced on this session, 2026-09-17.
Bug:
ServiceStatusProvidercallsrunCheck()from auseEffecton mount, thenre-runs every
intervalMs(default 5000ms) viasetInterval. The check isasync (
await checkHealth(...)), then firessetIsChecking(true)→setStatus→setLastCheckedAt→setIsChecking(false). The simplest test—
exposes the context to children— just renders the provider and readscontext, but the provider's initial
useEffectschedules an async stateupdate that lands after the synchronous
render(...)returns and beforethe test has a chance to wrap subsequent assertions in
act.The test still passes because React 19 + jsdom tolerates the unwrapped
update, but the warning fires every time and signals that the test isn't
exercising the full effect lifecycle. Worse:
setIntervalkeeps firingacross tests because vitest's default
clearMocks: truedoesn't resetReact timers; the 5s poll can outlive a test file and bleed into the next.
Fix consistent with existing patterns:
Wrap post-render assertions in
act(orwaitFor) so the initialrunCheckresolves before the test exits:For tests that exercise the timer, opt into
vi.useFakeTimers()andcall
vi.advanceTimersByTime(intervalMs)insideactto drive eachpoll deterministically. Add an
afterEach(() => vi.useRealTimers())toprevent leakage between tests.
The "polling" test already exists (
polls at the configured intervalMs)but relies on real timers — replace with
vi.useFakeTimers()+vi.advanceTimersByTime(intervalMs * 2).Acceptance:
npx vitest run src/components/ServiceStatusProvider.test.tsxemits zeronot wrapped in act(...)warnings.deterministically (use
vi.useFakeTimers, advance, assertcheckHealthwas called once and not again).