test(desktop): cover the context gauge fetching before a turn runs

This commit is contained in:
Brooklyn Nicholson
2026-08-14 18:33:43 -05:00
committed by brooklyn!
parent 25d1c8d740
commit c83061ba3d
@@ -1,12 +1,13 @@
import { act, cleanup, render, waitFor } from '@testing-library/react'
import { useState } from 'react'
import { cleanup, render, screen, waitFor } from '@testing-library/react'
import { renderHook } from '@testing-library/react'
import { afterEach, describe, expect, it, vi } from 'vitest'
import type { ContextBreakdown, UsageStats } from '@/types/hermes'
import { ContextUsagePanel } from './context-usage-panel'
import { useContextBreakdown } from './hooks/use-context-breakdown'
const initialUsage: UsageStats = {
const usage: UsageStats = {
calls: 1,
context_max: 272_000,
context_percent: 47,
@@ -30,64 +31,86 @@ afterEach(() => {
vi.restoreAllMocks()
})
describe('ContextUsagePanel', () => {
it('publishes once without refetching when publication recreates the callback', async () => {
describe('useContextBreakdown', () => {
it('fetches for a session that has not run a turn yet', async () => {
const requestGateway = vi.fn().mockResolvedValue(breakdown)
const published = vi.fn()
const renderedUsage: UsageStats[] = []
function Harness() {
const [currentUsage, setCurrentUsage] = useState(initialUsage)
renderedUsage.push(currentUsage)
const { result } = renderHook(() =>
useContextBreakdown({ busy: false, enabled: true, requestGateway, sessionId: 'runtime-1' })
)
return (
<ContextUsagePanel
currentUsage={currentUsage}
onUsageSnapshot={snapshot => {
published(snapshot)
setCurrentUsage(current => ({ ...current, ...snapshot }))
}}
requestGateway={requestGateway}
sessionId="runtime-1"
/>
)
}
render(<Harness />)
await waitFor(() => {
expect(published).toHaveBeenCalledWith({
context_max: 272_000,
context_percent: 89,
context_used: 241_400
})
expect(renderedUsage.at(-1)?.context_used).toBe(241_400)
})
await act(async () => {})
expect(requestGateway).toHaveBeenCalledTimes(1)
await waitFor(() => expect(result.current.breakdown).toEqual(breakdown))
expect(requestGateway).toHaveBeenCalledWith('session.context_breakdown', { session_id: 'runtime-1' })
})
it('refetches when the session or gateway requester changes', async () => {
const firstGateway = vi.fn().mockResolvedValue(breakdown)
const secondGateway = vi.fn().mockResolvedValue(breakdown)
it('does not fetch while the gauge is hidden, and fetches once it is shown', async () => {
const requestGateway = vi.fn().mockResolvedValue(breakdown)
const { rerender } = render(
<ContextUsagePanel currentUsage={initialUsage} requestGateway={firstGateway} sessionId="runtime-1" />
const { rerender } = renderHook(
({ enabled }) => useContextBreakdown({ busy: false, enabled, requestGateway, sessionId: 'runtime-1' }),
{ initialProps: { enabled: false } }
)
await waitFor(() => expect(firstGateway).toHaveBeenCalledTimes(1))
expect(requestGateway).not.toHaveBeenCalled()
rerender(<ContextUsagePanel currentUsage={initialUsage} requestGateway={firstGateway} sessionId="runtime-2" />)
rerender({ enabled: true })
await waitFor(() => {
expect(firstGateway).toHaveBeenCalledTimes(2)
expect(firstGateway).toHaveBeenLastCalledWith('session.context_breakdown', { session_id: 'runtime-2' })
})
await waitFor(() => expect(requestGateway).toHaveBeenCalledTimes(1))
})
rerender(<ContextUsagePanel currentUsage={initialUsage} requestGateway={secondGateway} sessionId="runtime-2" />)
it('skips the estimate mid-turn — the gateway streams measured usage then', () => {
const requestGateway = vi.fn().mockResolvedValue(breakdown)
await waitFor(() => expect(secondGateway).toHaveBeenCalledTimes(1))
renderHook(() => useContextBreakdown({ busy: true, enabled: true, requestGateway, sessionId: 'runtime-1' }))
expect(requestGateway).not.toHaveBeenCalled()
})
it('refetches on a session switch and never reports the previous session numbers', async () => {
const requestGateway = vi.fn().mockResolvedValue(breakdown)
const { rerender, result } = renderHook(
({ sessionId }) => useContextBreakdown({ busy: false, enabled: true, requestGateway, sessionId }),
{ initialProps: { sessionId: 'runtime-1' } }
)
await waitFor(() => expect(result.current.breakdown).toEqual(breakdown))
// Switching sessions must drop the numbers immediately — painting them
// under the new session's name would be a lie until its own fetch lands.
requestGateway.mockImplementation(() => new Promise(() => undefined))
rerender({ sessionId: 'runtime-2' })
expect(result.current.breakdown).toBeNull()
expect(requestGateway).toHaveBeenLastCalledWith('session.context_breakdown', { session_id: 'runtime-2' })
})
it('reports the measured occupancy the backend sends, not just the estimate', async () => {
// `context_used` on the payload is already the measured figure once a turn
// has run — the estimate is the backend's own fallback, not a second value
// the client has to choose between.
const measured: ContextBreakdown = { ...breakdown, context_used: 12_000 }
const requestGateway = vi.fn().mockResolvedValue(measured)
const { result } = renderHook(() =>
useContextBreakdown({ busy: false, enabled: true, requestGateway, sessionId: 'runtime-1' })
)
await waitFor(() => expect(result.current.breakdown?.context_used).toBe(12_000))
})
})
describe('ContextUsagePanel', () => {
it('renders the usage it is handed, so the popover matches the bar', () => {
render(<ContextUsagePanel breakdown={breakdown} loading={false} usage={usage} />)
expect(screen.getByText('47% Full')).toBeTruthy()
expect(screen.getByText('Conversation')).toBeTruthy()
})
it('says so when there is no breakdown rather than painting an empty bar', () => {
render(<ContextUsagePanel breakdown={null} loading={false} usage={usage} />)
expect(screen.getByText('No context data yet')).toBeTruthy()
})
})