162 lines
4.1 KiB
TypeScript
162 lines
4.1 KiB
TypeScript
import { afterEach, beforeEach, describe, expect, it, vi } from "bun:test";
|
|
import { stripVTControlCharacters } from "node:util";
|
|
import { resetSettingsForTest, Settings } from "@oh-my-pi/pi-coding-agent/config/settings";
|
|
import { StatusLineComponent } from "@oh-my-pi/pi-coding-agent/modes/components/status-line";
|
|
import { initTheme } from "@oh-my-pi/pi-coding-agent/modes/theme/theme";
|
|
import type { AgentSession } from "@oh-my-pi/pi-coding-agent/session/agent-session";
|
|
|
|
async function flushMicrotasks(): Promise<void> {
|
|
await Promise.resolve();
|
|
await Promise.resolve();
|
|
}
|
|
|
|
function makeSession(fetchUsageReports: (signal?: AbortSignal) => Promise<unknown>): AgentSession {
|
|
const messages: unknown[] = [];
|
|
return {
|
|
fetchUsageReports,
|
|
messages,
|
|
state: { messages, model: { contextWindow: 200_000 } },
|
|
model: { contextWindow: 200_000 },
|
|
isStreaming: false,
|
|
sessionManager: {
|
|
getUsageStatistics: () => ({
|
|
input: 0,
|
|
output: 0,
|
|
cacheRead: 0,
|
|
cacheWrite: 0,
|
|
totalTokens: 0,
|
|
orchestrationInput: 0,
|
|
orchestrationOutput: 0,
|
|
orchestrationCacheRead: 0,
|
|
premiumRequests: 0,
|
|
cost: 0,
|
|
}),
|
|
getSessionName: () => "test",
|
|
},
|
|
getAsyncJobSnapshot: () => ({ running: [] }),
|
|
getContextUsage: () => undefined,
|
|
contextUsageRevision: 0,
|
|
} as unknown as AgentSession;
|
|
}
|
|
|
|
function usageReport(percent: number): unknown[] {
|
|
return [
|
|
{
|
|
provider: "anthropic",
|
|
fetchedAt: Date.now(),
|
|
limits: [
|
|
{
|
|
id: "anthropic:5h",
|
|
label: "Claude 5 Hour",
|
|
scope: { provider: "anthropic", windowId: "5h" },
|
|
window: { id: "5h", label: "5h", resetsAt: Date.now() + 60_000 },
|
|
amount: { unit: "percent", usedFraction: percent / 100 },
|
|
},
|
|
],
|
|
},
|
|
];
|
|
}
|
|
|
|
function plain(text: string): string {
|
|
return stripVTControlCharacters(text);
|
|
}
|
|
|
|
describe("StatusLineComponent usage refresh", () => {
|
|
beforeEach(async () => {
|
|
resetSettingsForTest();
|
|
await Settings.init({ inMemory: true });
|
|
await initTheme();
|
|
vi.useFakeTimers();
|
|
});
|
|
|
|
afterEach(() => {
|
|
vi.useRealTimers();
|
|
resetSettingsForTest();
|
|
});
|
|
|
|
it("does not invoke usage fetching synchronously on the render path", async () => {
|
|
let calls = 0;
|
|
const component = new StatusLineComponent(
|
|
makeSession(async () => {
|
|
calls++;
|
|
return [];
|
|
}),
|
|
);
|
|
|
|
component.refreshUsageInBackground();
|
|
expect(calls).toBe(0);
|
|
|
|
vi.advanceTimersByTime(0);
|
|
await flushMicrotasks();
|
|
|
|
expect(calls).toBe(1);
|
|
});
|
|
|
|
it("passes a startup timeout signal to the background usage fetch", async () => {
|
|
let signal: AbortSignal | undefined;
|
|
const component = new StatusLineComponent(
|
|
makeSession(async nextSignal => {
|
|
signal = nextSignal;
|
|
return [];
|
|
}),
|
|
);
|
|
|
|
component.refreshUsageInBackground();
|
|
vi.advanceTimersByTime(0);
|
|
await flushMicrotasks();
|
|
|
|
expect(signal).toBeInstanceOf(AbortSignal);
|
|
});
|
|
|
|
it("backs off after the startup timeout when usage fetching hangs", async () => {
|
|
let calls = 0;
|
|
const component = new StatusLineComponent(
|
|
makeSession(() => {
|
|
calls++;
|
|
return Promise.withResolvers<unknown>().promise;
|
|
}),
|
|
);
|
|
|
|
component.refreshUsageInBackground();
|
|
vi.advanceTimersByTime(0);
|
|
await flushMicrotasks();
|
|
expect(calls).toBe(1);
|
|
|
|
component.refreshUsageInBackground();
|
|
expect(calls).toBe(1);
|
|
|
|
vi.advanceTimersByTime(2_000);
|
|
await flushMicrotasks();
|
|
|
|
component.refreshUsageInBackground();
|
|
vi.advanceTimersByTime(0);
|
|
await flushMicrotasks();
|
|
|
|
expect(calls).toBe(1);
|
|
});
|
|
|
|
it("applies late usage reports that resolve after the startup timeout", async () => {
|
|
const late = Promise.withResolvers<unknown>();
|
|
const component = new StatusLineComponent(makeSession(() => late.promise));
|
|
component.updateSettings({
|
|
preset: "custom",
|
|
leftSegments: ["usage"],
|
|
rightSegments: [],
|
|
separator: "powerline-thin",
|
|
});
|
|
|
|
component.refreshUsageInBackground();
|
|
vi.advanceTimersByTime(0);
|
|
await flushMicrotasks();
|
|
vi.advanceTimersByTime(2_000);
|
|
await flushMicrotasks();
|
|
|
|
expect(plain(component.getTopBorder(80).content)).not.toContain("5h");
|
|
|
|
late.resolve(usageReport(42));
|
|
await flushMicrotasks();
|
|
|
|
expect(plain(component.getTopBorder(80).content)).toContain("5h 42%");
|
|
});
|
|
});
|