test(ui): add unit tests for analytics feature
- Add analytics view helper function tests - Add analytics controller state tests - Add data type validation tests Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
ee2a576cf2
commit
6abd372a48
46
ui/src/ui/controllers/analytics.test.ts
Normal file
46
ui/src/ui/controllers/analytics.test.ts
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
import { describe, expect, it, vi } from "vitest";
|
||||||
|
import type { AnalyticsState } from "./analytics";
|
||||||
|
|
||||||
|
describe("analytics controller", () => {
|
||||||
|
it("AnalyticsState has expected properties", () => {
|
||||||
|
const mockClient = {
|
||||||
|
request: vi.fn().mockResolvedValue({
|
||||||
|
ok: true,
|
||||||
|
result: {
|
||||||
|
updatedAt: Date.now(),
|
||||||
|
days: 30,
|
||||||
|
daily: [],
|
||||||
|
totals: {
|
||||||
|
input: 0,
|
||||||
|
output: 0,
|
||||||
|
cacheRead: 0,
|
||||||
|
cacheWrite: 0,
|
||||||
|
totalTokens: 0,
|
||||||
|
totalCost: 0,
|
||||||
|
missingCostEntries: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
const state: AnalyticsState = {
|
||||||
|
client: mockClient as any,
|
||||||
|
analyticsLoading: false,
|
||||||
|
analyticsError: null,
|
||||||
|
analyticsData: null,
|
||||||
|
analyticsDays: 30,
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(state.analyticsLoading).toBe(false);
|
||||||
|
expect(state.analyticsDays).toBe(30);
|
||||||
|
expect(state.analyticsData).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should have valid day options", () => {
|
||||||
|
const validDays = [7, 14, 30, 90];
|
||||||
|
validDays.forEach((days) => {
|
||||||
|
expect(days).toBeGreaterThan(0);
|
||||||
|
expect(days).toBeLessThanOrEqual(90);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
99
ui/src/ui/views/analytics.test.ts
Normal file
99
ui/src/ui/views/analytics.test.ts
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
import { describe, expect, it } from "vitest";
|
||||||
|
import type { CostUsageDailyEntry, CostUsageSummary, AnalyticsProps } from "./analytics";
|
||||||
|
|
||||||
|
// Test the helper functions used in analytics view
|
||||||
|
describe("analytics view helpers", () => {
|
||||||
|
describe("formatNumber", () => {
|
||||||
|
// Inline the function for testing since it's not exported
|
||||||
|
const formatNumber = (n: number): string => {
|
||||||
|
if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(1)}M`;
|
||||||
|
if (n >= 1_000) return `${(n / 1_000).toFixed(1)}K`;
|
||||||
|
return n.toFixed(0);
|
||||||
|
};
|
||||||
|
|
||||||
|
it("formats millions", () => {
|
||||||
|
expect(formatNumber(1_500_000)).toBe("1.5M");
|
||||||
|
expect(formatNumber(2_000_000)).toBe("2.0M");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("formats thousands", () => {
|
||||||
|
expect(formatNumber(1_500)).toBe("1.5K");
|
||||||
|
expect(formatNumber(50_000)).toBe("50.0K");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("formats small numbers", () => {
|
||||||
|
expect(formatNumber(500)).toBe("500");
|
||||||
|
expect(formatNumber(0)).toBe("0");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("formatCost", () => {
|
||||||
|
const formatCost = (n: number): string => {
|
||||||
|
if (n === 0) return "$0.00";
|
||||||
|
if (n < 0.01) return `$${n.toFixed(4)}`;
|
||||||
|
return `$${n.toFixed(2)}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
it("formats zero", () => {
|
||||||
|
expect(formatCost(0)).toBe("$0.00");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("formats small costs", () => {
|
||||||
|
expect(formatCost(0.001)).toBe("$0.0010");
|
||||||
|
expect(formatCost(0.0001)).toBe("$0.0001");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("formats normal costs", () => {
|
||||||
|
expect(formatCost(1.5)).toBe("$1.50");
|
||||||
|
expect(formatCost(123.45)).toBe("$123.45");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("analytics data types", () => {
|
||||||
|
it("CostUsageSummary has expected structure", () => {
|
||||||
|
const summary: CostUsageSummary = {
|
||||||
|
updatedAt: Date.now(),
|
||||||
|
days: 30,
|
||||||
|
daily: [
|
||||||
|
{
|
||||||
|
date: "2026-01-01",
|
||||||
|
input: 1000,
|
||||||
|
output: 500,
|
||||||
|
cacheRead: 200,
|
||||||
|
cacheWrite: 100,
|
||||||
|
totalTokens: 1800,
|
||||||
|
totalCost: 0.05,
|
||||||
|
missingCostEntries: 0,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
totals: {
|
||||||
|
input: 1000,
|
||||||
|
output: 500,
|
||||||
|
cacheRead: 200,
|
||||||
|
cacheWrite: 100,
|
||||||
|
totalTokens: 1800,
|
||||||
|
totalCost: 0.05,
|
||||||
|
missingCostEntries: 0,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(summary.days).toBe(30);
|
||||||
|
expect(summary.daily).toHaveLength(1);
|
||||||
|
expect(summary.totals.totalTokens).toBe(1800);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("AnalyticsProps has expected structure", () => {
|
||||||
|
const props: AnalyticsProps = {
|
||||||
|
loading: false,
|
||||||
|
error: null,
|
||||||
|
data: null,
|
||||||
|
days: 30,
|
||||||
|
onDaysChange: () => {},
|
||||||
|
onRefresh: () => {},
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(props.loading).toBe(false);
|
||||||
|
expect(props.days).toBe(30);
|
||||||
|
});
|
||||||
|
});
|
||||||
Loading…
Reference in New Issue
Block a user