import { describe, expect, it } from "vite-plus/test"; import { buildDayColumns, niceScale } from "./UsageProviderChart"; import { providersWithUsage } from "./usageProviders"; describe("niceScale", () => { it("never puts the peak above the top of the scale", () => { // Regression: an earlier version stopped at the last step below the peak, // so the tallest day was drawn past the plot and clipped. for (const peak of [1122.71, 999, 1, 0.04, 1_400_000_000, 37.5, 5000, 100.001]) { const { max } = niceScale(peak, 4); expect(max, `peak ${peak}`).toBeGreaterThanOrEqual(peak); } }); it("starts at zero and ends at the maximum", () => { const { max, ticks } = niceScale(1122.71, 4); expect(ticks[0]).toBe(0); expect(ticks[ticks.length - 1]).toBeCloseTo(max, 6); }); it("uses evenly spaced 1/2/5 steps", () => { const { ticks } = niceScale(1122.71, 4); const steps = ticks.slice(1).map((tick, index) => tick - (ticks[index] ?? 0)); for (const step of steps) expect(step).toBeCloseTo(steps[0] ?? 0, 6); const [first = 0] = steps; const normalized = first / 10 ** Math.floor(Math.log10(first)); expect([1, 2, 5, 10]).toContain(Math.round(normalized)); }); it("keeps the tick count near the requested resolution", () => { const { ticks } = niceScale(1122.71, 4); expect(ticks.length).toBeGreaterThanOrEqual(3); expect(ticks.length).toBeLessThanOrEqual(7); }); it("degrades to a single zero tick with no data", () => { expect(niceScale(0, 4)).toEqual({ max: 0, ticks: [0] }); }); }); describe("buildDayColumns", () => { const days = ["2026-08-01", "2026-08-02", "2026-08-03"]; const byDay = new Map([ [ "2026-08-01", { day: "2026-08-01", costUsd: 30, totalTokens: 300, byProvider: new Map([ ["codex" as const, { costUsd: 10, totalTokens: 100 }], ["claude" as const, { costUsd: 20, totalTokens: 200 }], ]), }, ], // 2026-08-02 is deliberately absent: a day with no activity. [ "2026-08-03", { day: "2026-08-03", costUsd: 5, totalTokens: 50, byProvider: new Map([["claude" as const, { costUsd: 5, totalTokens: 50 }]]), }, ], ]); it("plots each day on its own", () => { expect(buildDayColumns(days, byDay, "cost").map((column) => column.total)).toEqual([30, 0, 5]); }); it("reads the requested metric", () => { expect(buildDayColumns(days, byDay, "tokens").map((column) => column.total)).toEqual([ 300, 0, 50, ]); }); it("keeps band values absolute rather than cumulative", () => { // Regression: the bands were once stack offsets, which drew Claude Code // permanently above Codex regardless of which provider spent more. const [first] = buildDayColumns(days, byDay, "cost"); expect(first?.bands).toEqual([ { provider: "codex", value: 10 }, { provider: "claude", value: 20 }, { provider: "grok", value: 0 }, ]); }); it("reports the total as the sum of its bands", () => { for (const column of buildDayColumns(days, byDay, "cost")) { const sum = column.bands.reduce((running, band) => running + band.value, 0); expect(column.total).toBeCloseTo(sum, 9); } }); }); describe("providersWithUsage", () => { it("omits providers with no cost or tokens", () => { expect( providersWithUsage([ { provider: "codex", costUsd: 0, totalTokens: 0 }, { provider: "claude", costUsd: 0, totalTokens: 200 }, ]), ).toEqual(["claude"]); }); }); describe("hourly chart columns", () => { it("zero-fills inactive hours and preserves hourly provider values", () => { const byHour = new Map([ [ "2026-08-11T09:37:00.000Z", { day: "2026-08-11", hourStart: "2026-08-11T09:37:00.000Z", costUsd: 4, totalTokens: 40, byProvider: new Map([["codex" as const, { costUsd: 4, totalTokens: 40 }]]), }, ], ]); expect( buildDayColumns( ["2026-08-11T08:37:00.000Z", "2026-08-11T09:37:00.000Z", "2026-08-11T10:37:00.000Z"], byHour, "cost", ).map((column) => column.total), ).toEqual([0, 4, 0]); }); });