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