Some checks failed
CI / windows node 24 / wine blocking (push) Has been skipped
CI / node 22.19 (push) Has been skipped
CI / node 26 (push) Has been skipped
CI / python 3.10 / keyless SDK (push) Has been skipped
CI / python runtime / release-shaped Linux x64 (push) Has been skipped
CI / wine apt cache (push) Successful in 7s
CI / serial / linux (push) Has been skipped
Deploy documentation / build (push) Failing after 1m25s
Deploy documentation / deploy (push) Has been skipped
Landlock Run / Matrix (push) Successful in 5s
Release (vendor) / Pack npm tarballs (push) Failing after 2m47s
Release (dsh) / Pack npm tarballs (push) Failing after 1m56s
Sandbox / sandbox e2e (landlock, ubuntu-24.04) (push) Failing after 1m57s
Sandbox / sandbox e2e (bwrap, ubuntu-latest) (push) Failing after 1m19s
Release (vendor) / Publish to npm (push) Has been skipped
Release (dsh) / Publish to npm (push) Has been skipped
CI / serial / windows (self-hosted standby) (push) Has been cancelled
CI / larger-runner-benchmark (16, linux, dsh-ubuntu-24-04-16core, typecheck) (push) Has been cancelled
Landlock Run / darwin (no platform package — degradation proof) (push) Has been cancelled
Landlock Run / ${{ matrix.platform }} (push) Has been cancelled
CI / node 24 / static (push) Has been cancelled
CI / node 24 / coverage (push) Has been cancelled
CI / node 24 / snapshots and artifacts (push) Has been cancelled
CI / windows node 24 / native complete (push) Has been cancelled
CI / serial / linux (self-hosted standby) (push) Has been cancelled
CI / serial / macos (push) Has been cancelled
CI / larger-runner-benchmark (16, windows, dsh-windows-2025-16core, production-site) (push) Has been cancelled
CI / larger-runner-benchmark (32, linux, dsh-ubuntu-24-04-32core, typecheck) (push) Has been cancelled
CI / larger-runner-benchmark (32, windows, dsh-windows-2025-32core, production-site) (push) Has been cancelled
CI / larger-runner-benchmark (4, linux, dsh-ubuntu-24-04-4core, typecheck) (push) Has been cancelled
CI / larger-runner-benchmark (4, windows, dsh-windows-2025-4core, production-site) (push) Has been cancelled
CI / larger-runner-benchmark (64, linux, dsh-ubuntu-24-04-64core, typecheck) (push) Has been cancelled
CI / larger-runner-benchmark (64, windows, dsh-windows-2025-64core, production-site) (push) Has been cancelled
CI / larger-runner-benchmark (8, windows, dsh-windows-2025-8core, production-site) (push) Has been cancelled
CI / larger-runner-benchmark (96, linux, dsh-ubuntu-24-04-96core, typecheck) (push) Has been cancelled
CI / larger-runner-benchmark (96, windows, dsh-windows-2025-96core, production-site) (push) Has been cancelled
CI / consolidated-runner-benchmark (16, linux, dsh-ubuntu-24-04-16core, 16) (push) Has been cancelled
CI / consolidated-runner-benchmark (16, windows, dsh-windows-2025-16core, 2) (push) Has been cancelled
CI / consolidated-runner-benchmark (32, linux, dsh-ubuntu-24-04-32core, 32) (push) Has been cancelled
CI / consolidated-runner-benchmark (32, windows, dsh-windows-2025-32core, 2) (push) Has been cancelled
CI / consolidated-runner-benchmark (4, linux, dsh-ubuntu-24-04-4core, 4) (push) Has been cancelled
CI / consolidated-runner-benchmark (4, windows, dsh-windows-2025-4core, 2) (push) Has been cancelled
CI / consolidated-runner-benchmark (64, linux, dsh-ubuntu-24-04-64core, 32) (push) Has been cancelled
CI / consolidated-runner-benchmark (64, windows, dsh-windows-2025-64core, 2) (push) Has been cancelled
CI / consolidated-runner-benchmark (8, linux, dsh-ubuntu-24-04-8core, 8) (push) Has been cancelled
CI / consolidated-runner-benchmark (8, windows, dsh-windows-2025-8core, 2) (push) Has been cancelled
CI / consolidated-runner-benchmark (96, linux, dsh-ubuntu-24-04-96core, 32) (push) Has been cancelled
CI / consolidated-runner-benchmark (96, windows, dsh-windows-2025-96core, 2) (push) Has been cancelled
CI / all checks passed (push) Has been cancelled
Sandbox / sandbox e2e (seatbelt, macos-latest) (push) Has been cancelled
CI / larger-runner-benchmark (8, linux, dsh-ubuntu-24-04-8core, typecheck) (push) Has been cancelled
Sandbox / sandbox e2e (landlock, ubuntu-24.04-arm) (push) Has been cancelled
E2E (real DeepSeek API) / e2e (push) Failing after 1m24s
- new economy and maximum agent presets with three-role pipeline skill - new packages/extensions/tool-lab (home-lab ComfyUI/Docling/Whishper tools) - new packages/subagent/subagent-cursor provider - openrouter balance UI with on-demand refresh - session projection context-seed boundary fold - regenerate docs catalogs; keep local searxng benchmark scripts
106 lines
4.3 KiB
TypeScript
106 lines
4.3 KiB
TypeScript
// @vitest-environment jsdom
|
|
// BalanceBadge presentation: polls an injected snapshot callback and renders
|
|
// 余额 in both widths, hiding unknown figures behind the empty placeholder,
|
|
// and re-reads through the injected refresh callback when the user clicks it.
|
|
|
|
import { act, cleanup, fireEvent, render, screen } from '@testing-library/react'
|
|
import { afterEach, describe, expect, it, vi } from 'vitest'
|
|
import { makeTranslate } from '@deepseek-ai/dsh-client-test-runtime'
|
|
import { zh as commonZh } from '@deepseek-ai/dsh-client-locale/src/locales/zh.ts'
|
|
import type { OpenRouterBalance } from '@deepseek-ai/dsh-openrouter-usage/client'
|
|
import { BALANCE_POLL_MS, BalanceBadge, type BalanceBadgeProps } from '../src/client/BalanceBadge.tsx'
|
|
import { zh } from '../src/client/locales.ts'
|
|
|
|
afterEach(() => {
|
|
vi.useRealTimers()
|
|
cleanup()
|
|
})
|
|
|
|
const t = makeTranslate(zh, commonZh)
|
|
|
|
function makeBalance(over: Partial<OpenRouterBalance> = {}): OpenRouterBalance {
|
|
return {
|
|
balanceUsd: 10.5,
|
|
label: 'prod-key',
|
|
usageTokens: 1000,
|
|
limitTokens: 100000,
|
|
isFreeTier: false,
|
|
updatedAt: Date.now(),
|
|
currency: 'USD',
|
|
...over,
|
|
}
|
|
}
|
|
|
|
function makeProps(over: Partial<BalanceBadgeProps> = {}) {
|
|
return {
|
|
wide: true,
|
|
snapshot: vi.fn(async () => makeBalance()),
|
|
refresh: vi.fn(async () => makeBalance()),
|
|
t,
|
|
...over,
|
|
} as unknown as Parameters<typeof BalanceBadge>[0]
|
|
}
|
|
|
|
describe('BalanceBadge', () => {
|
|
it('renders the balance label with the USD figure in wide mode', async () => {
|
|
render(<BalanceBadge {...makeProps()} />)
|
|
expect(await screen.findByLabelText('余额 $10.50')).toBeTruthy()
|
|
expect(screen.getByText('余额 $10.50')).toBeTruthy()
|
|
})
|
|
|
|
it('polls the snapshot callback on the balance interval', async () => {
|
|
vi.useFakeTimers()
|
|
const snapshot = vi.fn(async () => makeBalance())
|
|
render(<BalanceBadge {...makeProps({ snapshot })} />)
|
|
await act(async () => { await Promise.resolve() })
|
|
expect(snapshot).toHaveBeenCalledTimes(1)
|
|
await act(async () => { vi.advanceTimersByTime(BALANCE_POLL_MS) })
|
|
await act(async () => { await Promise.resolve() })
|
|
expect(snapshot).toHaveBeenCalledTimes(2)
|
|
})
|
|
|
|
it('renders only the rail marker in the collapsed sidebar', async () => {
|
|
render(<BalanceBadge {...makeProps({ wide: false })} />)
|
|
expect(await screen.findByLabelText('余额 $10.50')).toBeTruthy()
|
|
expect(screen.queryByText('余额 $10.50')).toBeNull()
|
|
})
|
|
|
|
it('shows the empty placeholder when no balance is known yet', async () => {
|
|
render(<BalanceBadge {...makeProps({ snapshot: vi.fn(async () => makeBalance({ balanceUsd: null })) })} />)
|
|
expect(await screen.findByLabelText('余额 ——')).toBeTruthy()
|
|
})
|
|
|
|
it('re-reads through refresh on click and renders the fetched figure', async () => {
|
|
const refresh = vi.fn(async () => makeBalance({ balanceUsd: 42.25 }))
|
|
render(<BalanceBadge {...makeProps({ refresh })} />)
|
|
const badge = await screen.findByLabelText('余额 $10.50')
|
|
await act(async () => { fireEvent.click(badge) })
|
|
expect(refresh).toHaveBeenCalledOnce()
|
|
expect(await screen.findByLabelText('余额 $42.25')).toBeTruthy()
|
|
})
|
|
|
|
it('ignores a click while a refresh is still outstanding', async () => {
|
|
let release: (value: OpenRouterBalance) => void = () => {}
|
|
const refresh = vi.fn(() => new Promise<OpenRouterBalance>((resolve) => { release = resolve }))
|
|
render(<BalanceBadge {...makeProps({ refresh })} />)
|
|
const badge = await screen.findByLabelText('余额 $10.50')
|
|
await act(async () => { fireEvent.click(badge) })
|
|
expect(badge.getAttribute('aria-busy')).toBe('true')
|
|
await act(async () => { fireEvent.click(badge) })
|
|
expect(refresh).toHaveBeenCalledOnce()
|
|
await act(async () => { release(makeBalance({ balanceUsd: 7 })) })
|
|
expect(badge.getAttribute('aria-busy')).toBe('false')
|
|
await act(async () => { fireEvent.click(badge) })
|
|
expect(refresh).toHaveBeenCalledTimes(2)
|
|
})
|
|
|
|
it('keeps the last-known figure when a refresh fails', async () => {
|
|
const refresh = vi.fn(async () => { throw new Error('boom') })
|
|
render(<BalanceBadge {...makeProps({ refresh })} />)
|
|
const badge = await screen.findByLabelText('余额 $10.50')
|
|
await act(async () => { fireEvent.click(badge) })
|
|
expect(await screen.findByLabelText('余额 $10.50')).toBeTruthy()
|
|
expect(badge.getAttribute('aria-busy')).toBe('false')
|
|
})
|
|
})
|