mirror of
https://github.com/getpaseo/paseo.git
synced 2026-10-23 01:32:40 +00:00
* Localize provider usage UI * fix: restore compact provider usage meter * fix: localize compact context percentage * fix(app): deduplicate context meter refresh * fix(app): localize provider usage time counts * fix(app): stabilize compact provider usage row * fix(app): localize provider usage plurals * test(app): allow locale-specific plural keys * fix(app): show compact quota without context usage * fix(app): preserve context overflow percentage * fix(app): truncate provider balance reset values * fix(app): add Korean provider usage translations * fix(app): refresh provider usage timing * fix(app): localize provider usage labels * fix(app): clarify compact usage policies * fix(app): localize monthly provider usage * test(app): require Arabic provider usage plurals * docs(i18n): keep migration guidance focused * fix(app): hide empty compact usage slot * fix(app): keep usage in context tooltip * fix(app): align usage with sidebar reports * test(app): pass usage timing clock explicitly * fix(app): restore context usage details * fix(app): make context usage tooltip interactive * fix(app): support keyboard tooltip controls * fix(app): keep focused tooltip controls open * fix(app): address usage localization review * fix(app): localize bulk usage refresh * fix(app): localize Claude session window * fix(app): complete provider usage localization * fix(app): keep mounted usage times and translations current Pass time and translation values into usage rendering so React Compiler invalidates cached labels when they change. Use the same localized window names and percentage meanings in sidebar summaries. * test(desktop): wait for restored tabs before shortcut input Document load completes before the workspace reconnects. Wait for the restored selection so Ctrl+1 reaches the mounted workspace rather than the startup screen. * test(app): allow the shared usage clock minute phase The browser clock advances independently of daemon timestamps, and the shared ticker samples on wall-minute boundaries. Assert the bounded one-to-two-minute age and matching countdown progress instead of a single phase-dependent value. --------- Co-authored-by: Michael Wu <michaelmwu@gmail.com> Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com>
157 lines
5.7 KiB
TypeScript
157 lines
5.7 KiB
TypeScript
import type { UsageReportEntry } from "@getpaseo/protocol/messages";
|
|
import { expect, test } from "../support/fixtures";
|
|
import { gotoAppShell, openSettings } from "../support/helpers/app";
|
|
import { getServerId } from "../support/helpers/server-id";
|
|
import { openSettingsHostSection } from "../support/helpers/settings";
|
|
import {
|
|
installUsageReportsFixture,
|
|
type UsageReportsFixture,
|
|
} from "../support/helpers/usage-reports";
|
|
import {
|
|
openUsageFromIcon,
|
|
refreshAllUsage,
|
|
showUsageAs,
|
|
} from "../support/helpers/usage-sidebar-item";
|
|
|
|
const ICON = '<svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="8" fill="currentColor"/></svg>';
|
|
|
|
function forcedRefreshCount(usage: UsageReportsFixture): number {
|
|
return usage.listRequests().filter((request) => request.forceRefresh).length;
|
|
}
|
|
|
|
function report(input: {
|
|
sourceId: string;
|
|
sourceLabel: string;
|
|
report:
|
|
| Partial<Extract<UsageReportEntry["report"], { status: "available" }>>
|
|
| Exclude<UsageReportEntry["report"], { status: "available" }>;
|
|
}): UsageReportEntry {
|
|
return {
|
|
id: `${input.sourceId}:account`,
|
|
account: { label: input.sourceId === "alpha" ? "dev@example.com" : undefined },
|
|
fetchedAt: "2026-01-01T00:00:00.000Z",
|
|
sourceId: input.sourceId,
|
|
sourceLabel: input.sourceLabel,
|
|
icon: ICON,
|
|
report:
|
|
input.report.status === "error" || input.report.status === "unavailable"
|
|
? input.report
|
|
: {
|
|
status: "available",
|
|
windows: [],
|
|
...input.report,
|
|
},
|
|
};
|
|
}
|
|
|
|
test.describe("usage settings", () => {
|
|
test("renders every report returned by usage.list_reports", async ({ page }) => {
|
|
test.setTimeout(120_000);
|
|
const serverId = getServerId();
|
|
const usage = await installUsageReportsFixture(page, {
|
|
lists: [
|
|
[
|
|
report({
|
|
sourceId: "alpha",
|
|
sourceLabel: "Alpha plan",
|
|
report: {
|
|
planLabel: "Max",
|
|
windows: [{ id: "session", label: "Session", usedPct: 7 }],
|
|
},
|
|
}),
|
|
report({
|
|
sourceId: "beta",
|
|
sourceLabel: "Beta plan",
|
|
report: {
|
|
planLabel: "Coding plan",
|
|
windows: [
|
|
{ id: "biweekly", label: "Biweekly", usedPct: 23 },
|
|
{ id: "daily", label: "Daily", remainingPct: 30 },
|
|
],
|
|
balances: [
|
|
{ id: "credits", label: "Credits", remaining: 1234, unit: "credits" },
|
|
{ id: "extra", label: "Extra usage", used: 5, limit: 20, unit: "usd" },
|
|
],
|
|
details: [{ id: "valid", label: "Valid until", value: "2026-12-31" }],
|
|
},
|
|
}),
|
|
report({
|
|
sourceId: "gamma",
|
|
sourceLabel: "Gamma plan",
|
|
report: { status: "error", error: "Gamma auth expired" },
|
|
}),
|
|
],
|
|
],
|
|
});
|
|
|
|
await gotoAppShell(page);
|
|
await openSettings(page);
|
|
await openSettingsHostSection(page, serverId, "usage");
|
|
await usage.waitForListRequests(1);
|
|
|
|
const card = page.getByTestId("usage-card");
|
|
await expect(card.getByText("Alpha plan", { exact: true })).toBeVisible({ timeout: 10_000 });
|
|
await expect(card.getByText("dev@example.com", { exact: true })).toBeVisible();
|
|
await expect(card.getByText("Beta plan", { exact: true })).toBeVisible();
|
|
await expect(card.getByText("70% used")).toBeVisible();
|
|
await expect(card.getByText("1,234 left", { exact: true })).toBeVisible();
|
|
await expect(card.getByText("$5.00 / $20.00", { exact: true })).toBeVisible();
|
|
await expect(card.getByText("2026-12-31", { exact: true })).toBeVisible();
|
|
await expect(card.getByText("Gamma auth expired", { exact: true })).toBeVisible();
|
|
|
|
// The shared percentages setting applies to the host section.
|
|
await expect(page.getByTestId("usage-options-menu")).toBeVisible();
|
|
const hostUsageUrl = page.url();
|
|
await gotoAppShell(page);
|
|
await openUsageFromIcon(page);
|
|
await showUsageAs(page, "remaining");
|
|
await page.goto(hostUsageUrl);
|
|
await expect(card.getByText("30% left")).toBeVisible();
|
|
await expect(card.getByText("93% left")).toBeVisible();
|
|
});
|
|
|
|
test("refresh forces a fresh report", async ({ page }) => {
|
|
test.setTimeout(120_000);
|
|
const serverId = getServerId();
|
|
const windows = (usedPct: number) => [{ id: "w", label: "Weekly", usedPct }];
|
|
// The sidebar summary and the section each load reports; only Refresh forces one.
|
|
const usage = await installUsageReportsFixture(page, {
|
|
lists: [
|
|
(request) => [
|
|
report({
|
|
sourceId: "alpha",
|
|
sourceLabel: "Alpha plan",
|
|
report: { windows: windows(request.forceRefresh ? 64 : 23) },
|
|
}),
|
|
],
|
|
],
|
|
});
|
|
|
|
await gotoAppShell(page);
|
|
await openSettings(page);
|
|
await openSettingsHostSection(page, serverId, "usage");
|
|
const card = page.getByTestId("usage-card");
|
|
await expect(card.getByText("23% used")).toBeVisible({ timeout: 10_000 });
|
|
|
|
await refreshAllUsage(page);
|
|
await expect.poll(() => forcedRefreshCount(usage)).toBe(1);
|
|
await expect(card.getByText("64% used")).toBeVisible();
|
|
});
|
|
|
|
test("asks to update a host without usage support and never calls it", async ({ page }) => {
|
|
test.setTimeout(120_000);
|
|
const serverId = getServerId();
|
|
const usage = await installUsageReportsFixture(page, { usageSupported: false });
|
|
|
|
await gotoAppShell(page);
|
|
await openSettings(page);
|
|
await openSettingsHostSection(page, serverId, "usage");
|
|
|
|
await expect(
|
|
// Names the host: "Update Laptop to see usage".
|
|
page.getByTestId("usage-card").getByText(/^Update (?!the host ).+ to see usage$/),
|
|
).toBeVisible({ timeout: 10_000 });
|
|
expect(usage.listRequests()).toHaveLength(0);
|
|
});
|
|
});
|