test(monitor-ui): 配额视图渲染与开关门控用例

- 新增 MonitorQuotaView.spec:tier window/label 的 i18n 映射与未知
  token 透出、阈值配色(≥90 红/≥75 黄/其余绿)、进度条宽度钳制、
  多币种余额与单余额回退、plan_level 徽章、错误截断与失败态不渲染 tiers
- 新增 MonitorCard.quota.spec:latest_quota 受系统开关门控,
  无快照或开关关闭时不渲染
- 既有工厂/断言补 check_mode、account_id;provider 网格断言 4→8
This commit is contained in:
Randark
2026-08-18 05:10:10 +00:00
parent a20e1c00cc
commit 302a10b886
5 changed files with 224 additions and 1 deletions
@@ -34,6 +34,8 @@ function makeMonitor(overrides: Partial<ChannelMonitor> = {}): ChannelMonitor {
extra_headers: {},
body_override_mode: 'off',
body_override: null,
check_mode: 'probe',
account_id: null,
...overrides,
}
}
@@ -0,0 +1,128 @@
import { mount } from '@vue/test-utils'
import { describe, expect, it, vi } from 'vitest'
import type { MonitorQuotaSnapshot } from '@/api/admin/channelMonitor'
import MonitorQuotaView from '../MonitorQuotaView.vue'
vi.mock('vue-i18n', async () => {
const actual = await vi.importActual<typeof import('vue-i18n')>('vue-i18n')
return {
...actual,
// te() 恒真:已知 token 直接返回 i18n key,便于断言 window/label 映射。
useI18n: () => ({ t: (key: string) => key, te: () => true }),
}
})
function makeSnapshot(overrides: Partial<MonitorQuotaSnapshot> = {}): MonitorQuotaSnapshot {
return {
source: 'usage',
success: true,
fetched_at: '2026-08-18T00:00:00Z',
...overrides,
}
}
describe('MonitorQuotaView', () => {
it('renders nothing without a snapshot', () => {
const wrapper = mount(MonitorQuotaView, { props: { snapshot: null } })
expect(wrapper.find('[data-testid="monitor-quota-view"]').exists()).toBe(false)
expect(wrapper.text()).toBe('')
})
it('maps tier window/label tokens through i18n and colors utilization', () => {
const wrapper = mount(MonitorQuotaView, {
props: {
snapshot: makeSnapshot({
tiers: [
{ window: '5h', used_percent: 42.4 },
{ window: '7d', label: 'pro', used_percent: 80 },
{ window: 'weekly', label: 'unknown-token', used_percent: 95 },
],
}),
},
})
const rows = wrapper.findAll('[data-testid="monitor-quota-view"] .flex.items-center')
// tier 行只在 success 且有数据时渲染
expect(rows.length).toBeGreaterThanOrEqual(3)
const text = wrapper.text()
// 已知 window token 走 i18n
expect(text).toContain('monitorCommon.quota.windows.5h')
expect(text).toContain('monitorCommon.quota.windows.7d')
// 已知 label token 拼成 label/window
expect(text).toContain('monitorCommon.quota.labels.pro/monitorCommon.quota.windows.7d')
// 未知 label 原样透出(前向兼容)
expect(text).toContain('unknown-token/monitorCommon.quota.windows.weekly')
// 百分比取整
expect(text).toContain('42%')
expect(text).toContain('80%')
expect(text).toContain('95%')
const html = wrapper.html()
// 阈值配色:≥90 红 / ≥75 黄 / 其余绿(与账号页 CNProviderQuotaCell 对齐)
expect(html).toContain('bg-emerald-500')
expect(html).toContain('bg-amber-500')
expect(html).toContain('bg-red-500')
})
it('clamps the tier bar width into 0-100', () => {
const wrapper = mount(MonitorQuotaView, {
props: {
snapshot: makeSnapshot({ tiers: [{ window: '5h', used_percent: 240 }] }),
},
})
expect(wrapper.html()).toContain('width: 100%')
})
it('shows the plan level badge and multi-currency balances', () => {
const wrapper = mount(MonitorQuotaView, {
props: {
snapshot: makeSnapshot({
plan_level: 'Max20',
balances: [
{ currency: 'CNY', balance: 12.5 },
{ currency: 'USD', balance: 0 },
],
}),
},
})
expect(wrapper.text()).toContain('Max20')
expect(wrapper.text()).toContain('12.50 CNY')
expect(wrapper.text()).toContain('0.00 USD')
// 余额为 0 用红色警示
expect(wrapper.html()).toContain('text-red-600')
})
it('falls back to the single balance + currency pair', () => {
const wrapper = mount(MonitorQuotaView, {
props: { snapshot: makeSnapshot({ balance: 3.2, currency: 'CNY' }) },
})
expect(wrapper.text()).toContain('3.20 CNY')
})
it('renders a truncated error state when the fetch failed', () => {
const longError = 'x'.repeat(60)
const wrapper = mount(MonitorQuotaView, {
props: {
snapshot: makeSnapshot({ success: false, error: longError }),
},
})
const error = wrapper.get('[data-testid="monitor-quota-error"]')
expect(error.text()).toBe(`${'x'.repeat(48)}…`)
expect(error.attributes('title')).toBe(longError)
})
it('keeps failed snapshots from rendering tier rows', () => {
const wrapper = mount(MonitorQuotaView, {
props: {
snapshot: makeSnapshot({
success: false,
tiers: [{ window: '5h', used_percent: 10 }],
}),
},
})
expect(wrapper.text()).not.toContain('10%')
})
})
@@ -0,0 +1,91 @@
import { mount } from '@vue/test-utils'
import { describe, expect, it, vi } from 'vitest'
import type { UserMonitorView } from '@/api/channelMonitor'
import MonitorCard from '../monitor/MonitorCard.vue'
const { isQuotaVisible } = vi.hoisted(() => ({
isQuotaVisible: vi.fn(() => false),
}))
vi.mock('@/utils/featureFlags', () => ({
isChannelMonitorQuotaVisible: () => isQuotaVisible(),
}))
vi.mock('vue-i18n', async () => {
const actual = await vi.importActual<typeof import('vue-i18n')>('vue-i18n')
return {
...actual,
useI18n: () => ({ t: (key: string) => key, te: () => true }),
}
})
function makeItem(overrides: Partial<UserMonitorView> = {}): UserMonitorView {
return {
id: 1,
name: 'claude-main',
provider: 'kimi',
group_name: '',
primary_model: 'quota',
primary_status: 'operational',
primary_latency_ms: null,
primary_ping_latency_ms: null,
availability_7d: 100,
extra_models: [],
timeline: [],
...overrides,
}
}
function mountCard(item: UserMonitorView) {
return mount(MonitorCard, {
props: {
item,
window: '7d',
availabilityValue: 100,
countdownSeconds: 0,
},
global: {
stubs: {
MonitorMetricPair: true,
MonitorAvailabilityRow: true,
MonitorTimeline: true,
},
},
})
}
describe('MonitorCard quota snapshot visibility', () => {
it('hides the quota block when the system switch is off even if data exists', () => {
isQuotaVisible.mockReturnValue(false)
const wrapper = mountCard(
makeItem({
latest_quota: { source: 'cn_quota', success: true, fetched_at: '2026-08-18T00:00:00Z' },
}),
)
expect(wrapper.find('[data-testid="monitor-quota-view"]').exists()).toBe(false)
})
it('renders the quota block when the switch is on and a snapshot exists', () => {
isQuotaVisible.mockReturnValue(true)
const wrapper = mountCard(
makeItem({
latest_quota: {
source: 'cn_quota',
success: true,
plan_level: 'kimi-plus',
tiers: [{ window: 'daily', label: 'requests', used_percent: 60 }],
fetched_at: '2026-08-18T00:00:00Z',
},
}),
)
expect(wrapper.find('[data-testid="monitor-quota-view"]').exists()).toBe(true)
expect(wrapper.text()).toContain('kimi-plus')
})
it('never renders the quota block without a snapshot', () => {
isQuotaVisible.mockReturnValue(true)
const wrapper = mountCard(makeItem())
expect(wrapper.find('[data-testid="monitor-quota-view"]').exists()).toBe(false)
})
})
@@ -96,6 +96,8 @@ function makeMonitor(overrides: Partial<ChannelMonitor> = {}): ChannelMonitor {
extra_headers: {},
body_override_mode: 'off',
body_override: null,
check_mode: 'probe',
account_id: null,
...overrides,
}
}
@@ -93,7 +93,7 @@ describe('channel monitor Grok provider', () => {
expect(PROVIDERS).toContain(PROVIDER_GROK)
const providerButtons = wrapper.findAll('[data-testid^="monitor-provider-"]')
expect(providerButtons).toHaveLength(4)
expect(providerButtons).toHaveLength(8)
expect(providerButtons[0].element.parentElement?.className).toContain('grid-cols-2')
expect(providerButtons[0].element.parentElement?.className).toContain('sm:grid-cols-4')