codeburn/app/renderer/components/Usd.test.tsx
Resham Joshi 246cb37c83
desktop: light theme default, refreshed type and spacing, plugin and plan cards (#1485)
Fresh installs now default to the light theme; an explicit theme choice is still honored. Type and spacing move one step up (larger body, titles, sidebar rows and page padding), sidebar shortcut chips move to hover tooltips, and primary text gets full contrast in both themes.

The Plugins page shows the Menu bar and Teams plugins as two cards side by side with embossed background art. The Menu bar card carries the Capacity Dock switch and Settings, Quit and Uninstall as icon buttons with tooltips, an info modal describing the menu bar and the Capacity Dock with the real supported-provider list, and it can install, update, open, quit and uninstall the menu bar app, versioned so an older one shows Update, with a Settings action that opens the menu bar app's own settings. The Teams card shows what is planned in an info modal and a Register for beta testing link.

The Plans page is redesigned as provider cards in a responsive grid, each with its own logo embossed. Connection status is now stable: a single empty or slow poll no longer flips a connected provider to disconnected, a connected provider keeps its quota bars through a transient refresh, and a stuck waiting resolves to an actionable state after twenty seconds. A login-expired error offers Connect, and Gemini's expired login is now a terminal actionable state instead of an endless wait, matching the menu bar.

CLI report, models and menubar-json numbers are identical to the cent before and after; only the Gemini quota error string changes.
2026-09-18 15:09:01 -07:00

96 lines
3.7 KiB
TypeScript

// @vitest-environment jsdom
import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { describe, expect, it } from 'vitest'
import { Usd, sumTokens, tokensOf } from './Usd'
const TOKENS = { inputTokens: 1_200, outputTokens: 34_000, cacheReadTokens: 2_500_000, cacheWriteTokens: 900, calls: 42 }
describe('Usd', () => {
it('renders the amount alone when there is no breakdown', () => {
render(<Usd value={12.5} />)
expect(screen.getByText('$12.50')).toBeInTheDocument()
expect(screen.queryByRole('tooltip')).toBeNull()
})
it('shows the four token rows on hover and hides them on leave', async () => {
const user = userEvent.setup()
render(<Usd value={12.5} tokens={TOKENS} />)
await user.hover(screen.getByText('$12.50'))
const tip = screen.getByRole('tooltip')
expect(tip).toHaveTextContent('Input1.2K')
expect(tip).toHaveTextContent('Output34K')
expect(tip).toHaveTextContent('Cache read2.5M')
expect(tip).toHaveTextContent('Cache write900')
expect(tip).toHaveTextContent('Calls42')
await user.unhover(screen.getByText('$12.50'))
expect(screen.queryByRole('tooltip')).toBeNull()
})
it('omits the Calls row when the breakdown carries no call count', async () => {
const user = userEvent.setup()
const { calls: _calls, ...noCalls } = TOKENS
render(<Usd value={12.5} tokens={noCalls} />)
await user.hover(screen.getByText('$12.50'))
const tip = screen.getByRole('tooltip')
expect(tip).toHaveTextContent('Cache write900')
expect(tip).not.toHaveTextContent('Calls')
})
it('opens on keyboard focus and closes on Escape', async () => {
const user = userEvent.setup()
render(<Usd value={12.5} tokens={TOKENS} />)
await user.tab()
expect(screen.getByText('$12.50')).toHaveFocus()
expect(screen.getByRole('tooltip')).toBeInTheDocument()
await user.keyboard('{Escape}')
expect(screen.queryByRole('tooltip')).toBeNull()
})
it('nested in an interactive row it is out of the tab order and hover-only', async () => {
const user = userEvent.setup()
render(<Usd value={12.5} tokens={TOKENS} nested />)
const trigger = screen.getByText('$12.50')
// Not its own tab stop, so it cannot swallow the row's Enter.
expect(trigger).toHaveAttribute('tabindex', '-1')
// Hover still reveals the breakdown.
await user.hover(trigger)
expect(screen.getByRole('tooltip')).toBeInTheDocument()
await user.unhover(trigger)
expect(screen.queryByRole('tooltip')).toBeNull()
// Tab lands elsewhere and focus never opens the popover.
await user.tab()
expect(trigger).not.toHaveFocus()
expect(screen.queryByRole('tooltip')).toBeNull()
})
})
describe('tokensOf / sumTokens', () => {
it('reads a breakdown only when every count is present', () => {
expect(tokensOf(TOKENS)).toEqual(TOKENS)
expect(tokensOf({ inputTokens: 1, outputTokens: 2, cacheReadTokens: 3 })).toBeNull()
expect(tokensOf(undefined)).toBeNull()
})
it('sums rows, and averages them when given a divisor', () => {
const rows = [TOKENS, TOKENS]
expect(sumTokens(rows)).toEqual({ inputTokens: 2_400, outputTokens: 68_000, cacheReadTokens: 5_000_000, cacheWriteTokens: 1_800, calls: 84 })
expect(sumTokens(rows, 2)).toEqual(TOKENS)
expect(sumTokens([])).toBeNull()
expect(sumTokens([TOKENS, { inputTokens: 1 }])).toBeNull()
})
it('leaves calls out when a row does not carry one, rather than adding a zero', () => {
const noCalls = { inputTokens: 100, outputTokens: 200, cacheReadTokens: 300, cacheWriteTokens: 400 }
expect(sumTokens([TOKENS, noCalls])).not.toHaveProperty('calls')
expect(sumTokens([noCalls])).not.toHaveProperty('calls')
})
})