| ... | ... | @@ -5,6 +5,8 @@ import { beforeEach, describe, expect, it, vi } from "vite-plus/test"; |
| 5 | 5 | |
| 6 | 6 | const testState = vi.hoisted(() => ({ |
| 7 | 7 | useUsage: vi.fn(), |
| 8 | metric: "cost" as "cost" | "tokens", |
| 9 | breakdown: "time" as "model" | "time", |
| 8 | 10 | })); |
| 9 | 11 | |
| 10 | 12 | vi.mock("react", async (importOriginal) => { |
| ... | ... | @@ -24,9 +26,11 @@ vi.mock("react", async (importOriginal) => { |
| 24 | 26 | untilTime: "2026-08-11T12:37:00.000Z", |
| 25 | 27 | }, |
| 26 | 28 | } |
| 27 | | : initial === "model" |
| 28 | | ? "time" |
| 29 | | : initial, |
| 29 | : initial === "cost" |
| 30 | ? testState.metric |
| 31 | : initial === "model" |
| 32 | ? testState.breakdown |
| 33 | : initial, |
| 30 | 34 | vi.fn(), |
| 31 | 35 | ]), |
| 32 | 36 | }; |
| ... | ... | @@ -72,10 +76,40 @@ const providerTotals = (codex: number, claude: number) => |
| 72 | 76 | ["claude", { costUsd: claude, totalTokens: claude * 1_000 }], |
| 73 | 77 | ] as const); |
| 74 | 78 | |
| 79 | const modelTotals = Object.freeze([ |
| 80 | { |
| 81 | model: "expensive-model", |
| 82 | provider: "claude" as const, |
| 83 | costUsd: 10, |
| 84 | totalTokens: 100, |
| 85 | records: 1, |
| 86 | costShare: 10 / 16, |
| 87 | }, |
| 88 | { |
| 89 | model: "token-heavy-model", |
| 90 | provider: "codex" as const, |
| 91 | costUsd: 5, |
| 92 | totalTokens: 1_000, |
| 93 | records: 1, |
| 94 | costShare: 5 / 16, |
| 95 | }, |
| 96 | { |
| 97 | model: "token-heavy-cheaper-model", |
| 98 | provider: "codex" as const, |
| 99 | costUsd: 1, |
| 100 | totalTokens: 1_000, |
| 101 | records: 1, |
| 102 | costShare: 1 / 16, |
| 103 | }, |
| 104 | ]); |
| 105 | |
| 75 | 106 | beforeEach(() => { |
| 107 | testState.metric = "cost"; |
| 108 | testState.breakdown = "time"; |
| 76 | 109 | testState.useUsage.mockReturnValue({ |
| 77 | 110 | merged: { |
| 78 | 111 | ...mergeUsage([], USAGE_CONTRACT_VERSION), |
| 112 | models: modelTotals, |
| 79 | 113 | hourly: [ |
| 80 | 114 | { |
| 81 | 115 | day: "2026-08-10", |
| ... | ... | @@ -110,4 +144,39 @@ describe("UsagePage hourly breakdown", () => { |
| 110 | 144 | expect(body).toContain("$13.00"); |
| 111 | 145 | expect(body.indexOf("$11.00")).toBeLessThan(body.indexOf("$13.00")); |
| 112 | 146 | }); |
| 147 | |
| 148 | it("keeps chronological ordering when the token metric is selected", () => { |
| 149 | testState.metric = "tokens"; |
| 150 | |
| 151 | const markup = renderToStaticMarkup(<UsagePage />); |
| 152 | const body = markup.match(/<tbody>(.*?)<\/tbody>/)?.[1] ?? ""; |
| 153 | |
| 154 | expect(body).toMatch(/\$11\.00.*\$13\.00/); |
| 155 | }); |
| 156 | }); |
| 157 | |
| 158 | describe("UsagePage model breakdown", () => { |
| 159 | it("sorts models by cost when the cost metric is selected", () => { |
| 160 | testState.breakdown = "model"; |
| 161 | |
| 162 | const markup = renderToStaticMarkup(<UsagePage />); |
| 163 | const body = markup.match(/<tbody>(.*?)<\/tbody>/)?.[1] ?? ""; |
| 164 | |
| 165 | expect(body).toMatch(/expensive-model.*token-heavy-model.*token-heavy-cheaper-model/); |
| 166 | }); |
| 167 | |
| 168 | it("sorts models by token usage when the token metric is selected", () => { |
| 169 | testState.metric = "tokens"; |
| 170 | testState.breakdown = "model"; |
| 171 | |
| 172 | const markup = renderToStaticMarkup(<UsagePage />); |
| 173 | const body = markup.match(/<tbody>(.*?)<\/tbody>/)?.[1] ?? ""; |
| 174 | |
| 175 | expect(body).toMatch(/token-heavy-model.*token-heavy-cheaper-model.*expensive-model/); |
| 176 | expect(modelTotals.map((model) => model.model)).toEqual([ |
| 177 | "expensive-model", |
| 178 | "token-heavy-model", |
| 179 | "token-heavy-cheaper-model", |
| 180 | ]); |
| 181 | }); |
| 113 | 182 | }); |