Skip to content

Commit f93f212

Browse files
committed
add tests
1 parent 7f3a1df commit f93f212

File tree

5 files changed

+907
-6
lines changed

5 files changed

+907
-6
lines changed

src/browser/stores/WorkspaceStore.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import {
1717
isRestoreToInput,
1818
} from "@/common/types/ipc";
1919
import { MapStore } from "./MapStore";
20-
import { cumUsageHistory } from "@/common/utils/tokens/displayUsage";
20+
import { collectUsageHistory } from "@/common/utils/tokens/displayUsage";
2121
import { WorkspaceConsumerManager } from "./WorkspaceConsumerManager";
2222
import type { ChatUsageDisplay } from "@/common/utils/tokens/usageAggregator";
2323
import type { TokenConsumer } from "@/common/types/chatStats";
@@ -432,7 +432,7 @@ export class WorkspaceStore {
432432

433433
const messages = aggregator.getAllMessages();
434434
const model = aggregator.getCurrentModel();
435-
const usageHistory = cumUsageHistory(messages, model);
435+
const usageHistory = collectUsageHistory(messages, model);
436436

437437
// Calculate total from usage history (now includes historical)
438438
const totalTokens = usageHistory.reduce(
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
import { describe, test, expect } from "bun:test";
2+
import { collectUsageHistory } from "./displayUsage";
3+
import { createMuxMessage, type MuxMessage } from "@/common/types/message";
4+
import type { LanguageModelV2Usage } from "@ai-sdk/provider";
5+
import type { ChatUsageDisplay } from "./usageAggregator";
6+
7+
// Helper to create assistant message with usage
8+
const createAssistant = (
9+
id: string,
10+
usage?: LanguageModelV2Usage,
11+
model?: string,
12+
historicalUsage?: ChatUsageDisplay
13+
): MuxMessage => {
14+
const msg = createMuxMessage(id, "assistant", "Response", {
15+
historySequence: 0,
16+
usage,
17+
model,
18+
historicalUsage,
19+
});
20+
return msg;
21+
};
22+
23+
describe("collectUsageHistory", () => {
24+
const basicUsage: LanguageModelV2Usage = {
25+
inputTokens: 100,
26+
outputTokens: 50,
27+
totalTokens: 150,
28+
};
29+
30+
test("returns empty array for empty messages", () => {
31+
expect(collectUsageHistory([])).toEqual([]);
32+
});
33+
34+
test("returns empty array when no assistant messages", () => {
35+
const userMsg = createMuxMessage("u1", "user", "Hello", { historySequence: 0 });
36+
expect(collectUsageHistory([userMsg])).toEqual([]);
37+
});
38+
39+
test("extracts usage from single assistant message", () => {
40+
const msg = createAssistant("a1", basicUsage, "claude-sonnet-4-5");
41+
const result = collectUsageHistory([msg]);
42+
43+
expect(result).toHaveLength(1);
44+
expect(result[0].model).toBe("claude-sonnet-4-5");
45+
expect(result[0].input.tokens).toBe(100);
46+
expect(result[0].output.tokens).toBe(50);
47+
});
48+
49+
test("extracts usage from multiple assistant messages", () => {
50+
const msg1 = createAssistant("a1", basicUsage, "claude-sonnet-4-5");
51+
const msg2 = createAssistant("a2", { ...basicUsage, inputTokens: 200 }, "claude-sonnet-4-5");
52+
const result = collectUsageHistory([msg1, msg2]);
53+
54+
expect(result).toHaveLength(2);
55+
expect(result[0].input.tokens).toBe(100);
56+
expect(result[1].input.tokens).toBe(200);
57+
});
58+
59+
test("skips assistant messages without usage", () => {
60+
const msg1 = createAssistant("a1", basicUsage, "claude-sonnet-4-5");
61+
const msg2 = createAssistant("a2", undefined, "claude-sonnet-4-5"); // No usage
62+
const msg3 = createAssistant("a3", basicUsage, "claude-sonnet-4-5");
63+
const result = collectUsageHistory([msg1, msg2, msg3]);
64+
65+
expect(result).toHaveLength(2); // msg2 excluded
66+
});
67+
68+
test("filters out user messages", () => {
69+
const userMsg = createMuxMessage("u1", "user", "Hello", { historySequence: 0 });
70+
const assistantMsg = createAssistant("a1", basicUsage, "claude-sonnet-4-5");
71+
const result = collectUsageHistory([userMsg, assistantMsg]);
72+
73+
expect(result).toHaveLength(1);
74+
});
75+
76+
test("uses fallbackModel when message has no model", () => {
77+
const msg = createAssistant("a1", basicUsage, undefined);
78+
const result = collectUsageHistory([msg], "fallback-model");
79+
80+
expect(result[0].model).toBe("fallback-model");
81+
});
82+
83+
test("defaults to 'unknown' when no model provided", () => {
84+
const msg = createAssistant("a1", basicUsage, undefined);
85+
const result = collectUsageHistory([msg]);
86+
87+
expect(result[0].model).toBe("unknown");
88+
});
89+
90+
test("prepends historical usage from compaction summary", () => {
91+
const historicalUsage: ChatUsageDisplay = {
92+
input: { tokens: 500, cost_usd: 0.01 },
93+
output: { tokens: 250, cost_usd: 0.02 },
94+
cached: { tokens: 0 },
95+
cacheCreate: { tokens: 0 },
96+
reasoning: { tokens: 0 },
97+
model: "historical-model",
98+
};
99+
100+
const msg = createAssistant("a1", basicUsage, "claude-sonnet-4-5", historicalUsage);
101+
const result = collectUsageHistory([msg]);
102+
103+
expect(result).toHaveLength(2);
104+
expect(result[0]).toBe(historicalUsage); // Historical comes first
105+
expect(result[1].model).toBe("claude-sonnet-4-5"); // Current message second
106+
});
107+
108+
test("uses latest historical usage when multiple messages have it", () => {
109+
const historical1: ChatUsageDisplay = {
110+
input: { tokens: 100 },
111+
output: { tokens: 50 },
112+
cached: { tokens: 0 },
113+
cacheCreate: { tokens: 0 },
114+
reasoning: { tokens: 0 },
115+
model: "first",
116+
};
117+
118+
const historical2: ChatUsageDisplay = {
119+
input: { tokens: 200 },
120+
output: { tokens: 100 },
121+
cached: { tokens: 0 },
122+
cacheCreate: { tokens: 0 },
123+
reasoning: { tokens: 0 },
124+
model: "second",
125+
};
126+
127+
const msg1 = createAssistant("a1", basicUsage, "model-1", historical1);
128+
const msg2 = createAssistant("a2", basicUsage, "model-2", historical2);
129+
const result = collectUsageHistory([msg1, msg2]);
130+
131+
expect(result).toHaveLength(3); // historical2 + msg1 + msg2
132+
expect(result[0]).toBe(historical2); // Latest historical usage wins
133+
expect(result[0].model).toBe("second");
134+
});
135+
136+
test("handles mixed message order correctly", () => {
137+
const userMsg = createMuxMessage("u1", "user", "Hello", { historySequence: 0 });
138+
const assistantMsg1 = createAssistant("a1", basicUsage, "model-1");
139+
const userMsg2 = createMuxMessage("u2", "user", "More", { historySequence: 2 });
140+
const assistantMsg2 = createAssistant("a2", basicUsage, "model-2");
141+
142+
const result = collectUsageHistory([userMsg, assistantMsg1, userMsg2, assistantMsg2]);
143+
144+
expect(result).toHaveLength(2);
145+
expect(result[0].model).toBe("model-1");
146+
expect(result[1].model).toBe("model-2");
147+
});
148+
});

src/common/utils/tokens/displayUsage.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ export function createDisplayUsage(
9292
};
9393
}
9494

95-
export function cumUsageHistory(
95+
export function collectUsageHistory(
9696
messages: MuxMessage[],
9797
fallbackModel?: string
9898
): ChatUsageDisplay[] {

0 commit comments

Comments
 (0)