Files
orca/mobile/src/components/account-usage-state.test.ts
T

234 lines
7.3 KiB
TypeScript

import { afterEach, describe, expect, it } from 'vitest'
import { mobileI18n } from '../i18n/mobile-i18n'
import {
getInactiveProviderUsage,
getUsageBarState,
getWindowResetLabel,
hasActiveProviderUsage,
hasRenderableUsage,
type AccountsSnapshot,
type InactiveAccountUsage,
type ProviderRateLimits
} from './account-usage-state'
const INITIAL_LOCALE = mobileI18n.language
afterEach(async () => {
await mobileI18n.changeLanguage(INITIAL_LOCALE)
})
function makeLimits(overrides: Partial<ProviderRateLimits> = {}): ProviderRateLimits {
return {
provider: 'claude',
session: null,
weekly: null,
monthly: null,
updatedAt: 0,
error: null,
status: 'idle',
...overrides
}
}
function makeSnapshot(
overrides: {
claudeLimits?: ProviderRateLimits | null
codexLimits?: ProviderRateLimits | null
claudeAccounts?: AccountsSnapshot['claude']['accounts']
codexAccounts?: AccountsSnapshot['codex']['accounts']
inactiveClaudeAccounts?: InactiveAccountUsage[]
inactiveCodexAccounts?: InactiveAccountUsage[]
} = {}
): AccountsSnapshot {
return {
claude: {
accounts: overrides.claudeAccounts ?? [],
activeAccountId: null,
activeAccountIdsByRuntime: { host: null, wsl: {} }
},
codex: {
accounts: overrides.codexAccounts ?? [],
activeAccountId: null,
activeAccountIdsByRuntime: { host: null, wsl: {} }
},
rateLimits: {
claude: overrides.claudeLimits ?? null,
codex: overrides.codexLimits ?? null,
claudeTarget: { runtime: 'host', wslDistro: null },
codexTarget: { runtime: 'host', wslDistro: null },
inactiveClaudeAccounts: overrides.inactiveClaudeAccounts ?? [],
inactiveCodexAccounts: overrides.inactiveCodexAccounts ?? []
}
}
}
describe('hasActiveProviderUsage', () => {
it('is false when there are no rate limits at all', () => {
expect(hasActiveProviderUsage(null)).toBe(false)
})
it('is true when a session window has data', () => {
expect(
hasActiveProviderUsage(
makeLimits({
status: 'ok',
session: { usedPercent: 12, windowMinutes: 300, resetsAt: null, resetDescription: null }
})
)
).toBe(true)
})
it('is true when a successful fetch returned ok even with empty windows', () => {
expect(hasActiveProviderUsage(makeLimits({ status: 'ok' }))).toBe(true)
})
it('is false for an unavailable/error provider with no window data (no creds)', () => {
expect(hasActiveProviderUsage(makeLimits({ status: 'unavailable' }))).toBe(false)
expect(hasActiveProviderUsage(makeLimits({ status: 'error', error: 'nope' }))).toBe(false)
})
})
describe('hasRenderableUsage', () => {
it('is true when the provider has at least one managed account', () => {
const snapshot = makeSnapshot({
claudeAccounts: [{ id: 'a', email: 'x@y.z' }]
})
expect(hasRenderableUsage(snapshot, 'claude')).toBe(true)
})
// The bug: system-default auth has zero managed accounts but real usage data,
// and the home screen used to hide it entirely.
it('is true with zero managed accounts when active rate-limit data exists (system default)', () => {
const snapshot = makeSnapshot({
codexLimits: makeLimits({
provider: 'codex',
status: 'ok',
session: { usedPercent: 40, windowMinutes: 300, resetsAt: null, resetDescription: null }
})
})
expect(hasRenderableUsage(snapshot, 'codex')).toBe(true)
})
it('is false with zero accounts and no usable rate-limit data', () => {
const snapshot = makeSnapshot({
claudeLimits: makeLimits({ status: 'unavailable' })
})
expect(hasRenderableUsage(snapshot, 'claude')).toBe(false)
expect(hasRenderableUsage(makeSnapshot(), 'claude')).toBe(false)
})
})
describe('getInactiveProviderUsage', () => {
it('returns inactive usage using the runtime rateLimits payload shape', () => {
const limits = makeLimits({
status: 'ok',
session: { usedPercent: 52, windowMinutes: 300, resetsAt: null, resetDescription: null }
})
const snapshot = makeSnapshot({
inactiveClaudeAccounts: [
{ accountId: 'account-1', rateLimits: limits, updatedAt: 123, isFetching: false }
]
})
expect(getInactiveProviderUsage(snapshot, 'claude', 'account-1')?.rateLimits).toBe(limits)
})
})
describe('getWindowResetLabel', () => {
const now = 1_700_000_000_000
const min = 60_000
const hour = 60 * min
const day = 24 * hour
function makeWindow(resetsAt: number | null): ProviderRateLimits['session'] {
return { usedPercent: 13, windowMinutes: 300, resetsAt, resetDescription: null }
}
it('is null when there are no limits or the window has no reset timestamp', () => {
expect(getWindowResetLabel(null, 'session', now)).toBe(null)
expect(getWindowResetLabel(makeLimits({ status: 'ok' }), 'session', now)).toBe(null)
expect(
getWindowResetLabel(makeLimits({ status: 'ok', session: makeWindow(null) }), 'session', now)
).toBe(null)
})
it('formats minutes, hours+minutes, and days+hours like the desktop tooltip', () => {
expect(
getWindowResetLabel(makeLimits({ session: makeWindow(now + 47 * min) }), 'session', now)
).toBe('Resets in 47m')
expect(
getWindowResetLabel(
makeLimits({ session: makeWindow(now + 3 * hour + 54 * min) }),
'session',
now
)
).toBe('Resets in 3h 54m')
expect(
getWindowResetLabel(
makeLimits({ weekly: makeWindow(now + 6 * day + 7 * hour) }),
'weekly',
now
)
).toBe('Resets in 6d 7h')
})
it('formats exact hours and exact days without a zero remainder', () => {
expect(
getWindowResetLabel(makeLimits({ session: makeWindow(now + 2 * hour) }), 'session', now)
).toBe('Resets in 2h')
expect(
getWindowResetLabel(makeLimits({ weekly: makeWindow(now + 7 * day) }), 'weekly', now)
).toBe('Resets in 7d')
})
it('reports "Resets now" for a reset timestamp in the past', () => {
expect(
getWindowResetLabel(makeLimits({ session: makeWindow(now - min) }), 'session', now)
).toBe('Resets now')
})
it('localizes the complete reset countdown sentence', async () => {
await mobileI18n.changeLanguage('es')
expect(
getWindowResetLabel(
makeLimits({ session: makeWindow(now + 3 * hour + 54 * min) }),
'session',
now
)
).toBe('Se restablece en 3h 54m')
expect(
getWindowResetLabel(makeLimits({ session: makeWindow(now - min) }), 'session', now)
).toBe('Se restablece ahora')
})
it('reads the requested window only', () => {
const limits = makeLimits({ session: makeWindow(now + hour) })
expect(getWindowResetLabel(limits, 'weekly', now)).toBe(null)
})
})
describe('getUsageBarState', () => {
it('keeps stale window data visible during a transient error', () => {
const bar = getUsageBarState(
makeLimits({
status: 'error',
error: 'temporarily unavailable',
session: { usedPercent: 72, windowMinutes: 300, resetsAt: null, resetDescription: null }
}),
'session'
)
expect(bar).toEqual({ usedPercent: 72, unavailable: false, loading: false })
})
it('shows loading for a fetching provider without a window', () => {
expect(getUsageBarState(makeLimits({ status: 'fetching' }), 'weekly')).toEqual({
usedPercent: null,
unavailable: false,
loading: true
})
})
})