vibe-proxy/frontend/tests/dashboardMetrics.test.ts
2026-08-24 00:10:41 +02:00

133 lines
4.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { describe, expect, test } from 'vitest';
import { formatCompactNumber, formatPercent } from '../src/utils/format';
import { getProviderKeyCounts } from '../src/features/dashboard/hooks/useDashboardOverview';
import {
axisMax,
niceCeil,
providerLabel,
splitWindowMinutes,
toneForSuccessRate,
} from '../src/features/dashboard/utils';
describe('formatCompactNumber', () => {
test('leaves values below one thousand alone', () => {
expect(formatCompactNumber(0)).toBe('0');
expect(formatCompactNumber(999)).toBe('999');
});
test('compacts with a single decimal and no redundant .0', () => {
expect(formatCompactNumber(1000)).toBe('1K');
expect(formatCompactNumber(1284)).toBe('1.3K');
expect(formatCompactNumber(12_900)).toBe('12.9K');
expect(formatCompactNumber(1_500_000)).toBe('1.5M');
});
test('carries into the next tier instead of rendering 1000K', () => {
expect(formatCompactNumber(999_999)).toBe('1M');
expect(formatCompactNumber(999_999_999)).toBe('1B');
});
test('keeps the sign and survives non-finite input', () => {
expect(formatCompactNumber(-1500)).toBe('-1.5K');
expect(formatCompactNumber(Number.NaN)).toBe('0');
});
});
describe('formatPercent', () => {
test('trims trailing zeros but keeps meaningful decimals', () => {
expect(formatPercent(100)).toBe('100%');
expect(formatPercent(99.5)).toBe('99.5%');
expect(formatPercent(0)).toBe('0%');
});
test('renders an em dash for non-finite rates', () => {
expect(formatPercent(Number.NaN)).toBe('—');
});
});
describe('niceCeil', () => {
test('rounds up onto the step ladder', () => {
expect(niceCeil(1)).toBe(1);
expect(niceCeil(7)).toBe(8);
expect(niceCeil(12)).toBe(15);
expect(niceCeil(48)).toBe(50);
expect(niceCeil(320)).toBe(400);
});
test('never returns zero, so bar heights cannot divide by zero', () => {
expect(niceCeil(0)).toBe(1);
expect(niceCeil(-5)).toBe(1);
});
});
describe('axisMax', () => {
test('lands every gridline on a whole number', () => {
// 峰值 112 → 上限 120刻度 0/30/60/90/120而不是浪费半张图的 200
expect(axisMax(112, 4)).toBe(120);
expect(axisMax(7, 4)).toBe(8);
expect(axisMax(1533, 4)).toBe(1600);
});
test('keeps the axis just above the peak', () => {
for (const peak of [1, 3, 9, 17, 64, 112, 250, 999, 4321]) {
const max = axisMax(peak, 4);
expect(max).toBeGreaterThanOrEqual(peak);
// 上限不应超过峰值的两倍,否则柱子被压得太矮。
// 峰值极小时受「每格至少 1」约束下限就是间隔数本身。
expect(max).toBeLessThanOrEqual(Math.max(4, peak * 2));
// 每格都必须是整数
expect(Number.isInteger(max / 4)).toBe(true);
}
});
test('degrades safely with no traffic', () => {
expect(axisMax(0, 4)).toBe(4);
expect(axisMax(-3, 4)).toBe(4);
});
});
describe('toneForSuccessRate', () => {
test('maps a rate onto a severity band', () => {
expect(toneForSuccessRate(null)).toBe('idle');
expect(toneForSuccessRate(100)).toBe('good');
expect(toneForSuccessRate(95)).toBe('good');
expect(toneForSuccessRate(94.9)).toBe('warning');
expect(toneForSuccessRate(80)).toBe('warning');
expect(toneForSuccessRate(79.9)).toBe('critical');
});
});
describe('splitWindowMinutes', () => {
test('splits the rolling window into hours and minutes', () => {
expect(splitWindowMinutes(200)).toEqual({ hours: 3, minutes: 20 });
expect(splitWindowMinutes(60)).toEqual({ hours: 1, minutes: 0 });
expect(splitWindowMinutes(40)).toEqual({ hours: 0, minutes: 40 });
});
});
describe('provider key counts', () => {
test('includes native Interactions API keys in the dashboard total inputs', () => {
const counts = getProviderKeyCounts({
geminiApiKeys: [{ apiKey: 'gemini-key' }],
interactionsApiKeys: [{ apiKey: 'interactions-1' }, { apiKey: 'interactions-2' }],
codexApiKeys: [{ apiKey: 'codex-key' }],
});
expect(counts.interactions).toBe(2);
expect(Object.values(counts).reduce((sum, count) => sum + count, 0)).toBe(4);
});
});
describe('providerLabel', () => {
test('uses the brand spelling for known providers', () => {
expect(providerLabel('xai', 'Unattributed')).toBe('xAI');
expect(providerLabel('aistudio', 'Unattributed')).toBe('AI Studio');
expect(providerLabel('gemini-interactions', 'Unattributed')).toBe('Interactions API');
});
test('falls back to a capitalised id, and localises unknown', () => {
expect(providerLabel('somenewbrand', 'Unattributed')).toBe('Somenewbrand');
expect(providerLabel('unknown', 'Unattributed')).toBe('Unattributed');
expect(providerLabel('', 'Unattributed')).toBe('Unattributed');
});
});