Repository navigation
fix(status-bar): show Antigravity's model-group pools instead of an empty segment #24074
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,123 @@ | ||
| import { renderToStaticMarkup } from 'react-dom/server' | ||
| import { describe, expect, it, vi } from 'vitest' | ||
| import type { ProviderRateLimits, RateLimitWindow } from '../../../../shared/rate-limit-types' | ||
|
|
||
| vi.mock('@/i18n/i18n', () => ({ | ||
| i18n: { language: 'en' }, | ||
| translate: (_key: string, fallback: string, values?: Record<string, string>) => { | ||
| let result = fallback | ||
| for (const [key, value] of Object.entries(values ?? {})) { | ||
| result = result.replace(`{{${key}}}`, value) | ||
| } | ||
| return result | ||
| } | ||
| })) | ||
|
|
||
| vi.mock('@/lib/agent-catalog', () => ({ | ||
| AgentIcon: () => null | ||
| })) | ||
|
|
||
| vi.mock('../../store', () => ({ | ||
| useAppStore: (selector: (state: { usagePercentageDisplay: 'used' | 'remaining' }) => unknown) => | ||
| selector({ usagePercentageDisplay: 'used' }) | ||
| })) | ||
|
|
||
| const WEEKLY_MINUTES = 10_080 | ||
|
|
||
| function weeklyWindow(usedPercent: number): RateLimitWindow { | ||
| return { usedPercent, windowMinutes: WEEKLY_MINUTES, resetsAt: null, resetDescription: null } | ||
| } | ||
|
|
||
| /** | ||
| * The record `agy -p "/usage"` produces for a tier metered weekly only, with the Gemini pool | ||
| * genuinely exhausted — captured from agy 1.2.11. | ||
| */ | ||
| function antigravityLimits(overrides: Partial<ProviderRateLimits> = {}): ProviderRateLimits { | ||
| return { | ||
| provider: 'antigravity', | ||
| session: null, | ||
| weekly: weeklyWindow(100), | ||
| buckets: [ | ||
| { name: 'Gemini Models', ...weeklyWindow(100) }, | ||
| { name: 'Claude and GPT models', ...weeklyWindow(0) } | ||
| ], | ||
| updatedAt: Date.now(), | ||
| error: null, | ||
| status: 'ok', | ||
| ...overrides | ||
| } | ||
| } | ||
|
|
||
| describe('Antigravity status-bar segment', () => { | ||
| it('renders both model-group pools by name', async () => { | ||
| // Why: the verbose bucket allowlist was written for Gemini's experimental models, so | ||
| // Antigravity's pools — whose names come from the account's tier and cannot be enumerated | ||
| // ahead of time — were filtered out and the segment showed no number at all. | ||
| const { ProviderSegment } = await import('./StatusBar') | ||
| const markup = renderToStaticMarkup( | ||
| <ProviderSegment p={antigravityLimits()} compact={false} display="used" mode="verbose" /> | ||
| ) | ||
|
|
||
| expect(markup).toContain('Gemini Models') | ||
| expect(markup).toContain('Claude and GPT models') | ||
| expect(markup).toContain('100%') | ||
| }) | ||
|
|
||
| it('shows the weekly window when a tier reports no session pool', async () => { | ||
| // Why: the verbose fallback chain was `session ?? monthly`, so a weekly-only provider with a | ||
| // real limit rendered an empty segment. | ||
| const { ProviderSegment } = await import('./StatusBar') | ||
| const markup = renderToStaticMarkup( | ||
| <ProviderSegment | ||
| p={antigravityLimits({ buckets: [] })} | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time! |
||
| compact={false} | ||
| display="used" | ||
| mode="verbose" | ||
| /> | ||
| ) | ||
|
|
||
| expect(markup).toContain('100%') | ||
| }) | ||
|
|
||
| it('still renders a reading when only the weekly window is known', async () => { | ||
| const { ProviderSegment } = await import('./StatusBar') | ||
| const markup = renderToStaticMarkup( | ||
| <ProviderSegment | ||
| p={antigravityLimits({ buckets: undefined })} | ||
| compact={false} | ||
| display="used" | ||
| mode="verbose" | ||
| /> | ||
| ) | ||
|
|
||
| expect(markup).toContain('100%') | ||
| }) | ||
|
|
||
| it('does not widen the allowlist for providers that rely on it', async () => { | ||
| // Why: Gemini's experimental buckets are still filtered; only providers whose buckets are the | ||
| // whole meter bypass the allowlist. | ||
| const { ProviderSegment } = await import('./StatusBar') | ||
| const markup = renderToStaticMarkup( | ||
| <ProviderSegment | ||
| p={{ | ||
| provider: 'gemini', | ||
| session: weeklyWindow(12), | ||
| weekly: null, | ||
| buckets: [ | ||
| { name: 'Pro', ...weeklyWindow(30) }, | ||
| { name: 'Some Experimental Model', ...weeklyWindow(80) } | ||
| ], | ||
| updatedAt: Date.now(), | ||
| error: null, | ||
| status: 'ok' | ||
| }} | ||
| compact={false} | ||
| display="used" | ||
| mode="verbose" | ||
| /> | ||
| ) | ||
|
|
||
| expect(markup).toContain('Pro') | ||
| expect(markup).not.toContain('Some Experimental Model') | ||
| }) | ||
| }) | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The rationale here doesn't match the code path this test takes. With
buckets: [](andbuckets: undefinedin the next test),VerboseProviderUsageskips thep.buckets.length > 0branch and goes to the second branch, which already renderedp.weeklybefore this PR. I verified this: reverting the changedsession ?? monthly ?? weeklyline tosession ?? monthlystill leaves all four tests passing, so the new?? p.weeklyfallback has no coverage.To actually pin it, a fixture needs buckets present but all filtered (e.g. an unrecognised name) with
sessionandmonthlynull andweeklyset — only then does execution reach the fallback line.Technical details