mirror of
https://github.com/cline/cline.git
synced 2026-10-28 02:58:44 +08:00
fix(desktop): align usage table columns CLINE-2996 (#13325)
* fix(desktop): align usage table columns * fix(desktop): show usage link for empty history
This commit is contained in:
@@ -5,10 +5,13 @@ import { createRoot, type Root } from "react-dom/client";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { AccountView } from "./account-view";
|
||||
|
||||
const { invoke } = vi.hoisted(() => ({ invoke: vi.fn() }));
|
||||
const { invoke, openExternalUrl } = vi.hoisted(() => ({
|
||||
invoke: vi.fn(),
|
||||
openExternalUrl: vi.fn(),
|
||||
}));
|
||||
vi.mock("@/lib/desktop-client", () => ({
|
||||
desktopClient: { invoke },
|
||||
openExternalUrl: vi.fn(),
|
||||
openExternalUrl,
|
||||
}));
|
||||
|
||||
let container: HTMLDivElement;
|
||||
@@ -17,6 +20,7 @@ let root: Root;
|
||||
beforeEach(() => {
|
||||
Object.assign(globalThis, { IS_REACT_ACT_ENVIRONMENT: true });
|
||||
invoke.mockReset();
|
||||
openExternalUrl.mockReset();
|
||||
container = document.createElement("div");
|
||||
document.body.appendChild(container);
|
||||
root = createRoot(container);
|
||||
@@ -28,6 +32,60 @@ afterEach(async () => {
|
||||
vi.restoreAllMocks();
|
||||
});
|
||||
|
||||
describe("AccountView usage table", () => {
|
||||
it("opens the full usage dashboard from the empty table footer", async () => {
|
||||
invoke.mockImplementation(
|
||||
async (_command: string, args?: Record<string, unknown>) => {
|
||||
switch (args?.operation) {
|
||||
case "fetchMe":
|
||||
return {
|
||||
id: "user-1",
|
||||
email: "beatrix@cline.bot",
|
||||
displayName: "Beatrix",
|
||||
createdAt: "2024-01-01T00:00:00Z",
|
||||
updatedAt: "2024-01-01T00:00:00Z",
|
||||
organizations: [],
|
||||
};
|
||||
case "fetchBalance":
|
||||
return { balance: 5_000_000 };
|
||||
case "fetchUserOrganizations":
|
||||
return [];
|
||||
case "fetchUsageTransactions":
|
||||
return [];
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
root.render(<AccountView />);
|
||||
});
|
||||
await vi.waitFor(() => {
|
||||
expect(container.textContent).toContain("Beatrix");
|
||||
});
|
||||
|
||||
const usageTab = Array.from(container.querySelectorAll("button")).find(
|
||||
(button) => button.textContent === "usage",
|
||||
);
|
||||
expect(usageTab).toBeDefined();
|
||||
await act(async () => usageTab?.click());
|
||||
|
||||
await vi.waitFor(() => {
|
||||
expect(container.textContent).toContain("See More");
|
||||
expect(container.textContent).toContain("No usage transactions yet.");
|
||||
});
|
||||
const seeMoreButton = Array.from(container.querySelectorAll("button")).find(
|
||||
(button) => button.textContent?.includes("See More"),
|
||||
);
|
||||
await act(async () => seeMoreButton?.click());
|
||||
|
||||
expect(openExternalUrl).toHaveBeenCalledWith(
|
||||
"https://app.cline.bot/dashboard/usage",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("AccountView signed-out state", () => {
|
||||
it("renders the sign-in prompt from the typed result and stops fetching account data", async () => {
|
||||
invoke.mockResolvedValue({
|
||||
|
||||
@@ -32,6 +32,7 @@ import { invalidateProviderCatalogCache } from "@/lib/provider-model-catalog";
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const DASHBOARD_URL = "https://app.cline.bot/dashboard";
|
||||
const USAGE_DASHBOARD_URL = "https://app.cline.bot/dashboard/usage";
|
||||
const USER_CREDITS_URL =
|
||||
"https://app.cline.bot/dashboard/account?tab=credits&redirect=true";
|
||||
const ORGANIZATION_CREDITS_URL =
|
||||
@@ -740,26 +741,24 @@ export function AccountView() {
|
||||
</p>
|
||||
{usageLoading && renderLoading()}
|
||||
{usageError && renderError(usageError, loadUsage)}
|
||||
{!usageLoading &&
|
||||
!usageError &&
|
||||
usageLoaded &&
|
||||
(usageTransactions.length === 0 ? (
|
||||
<p className="py-8 text-center text-sm text-muted-foreground">
|
||||
No usage transactions yet.
|
||||
</p>
|
||||
) : (
|
||||
<div className="rounded-lg border border-border overflow-hidden">
|
||||
<div className="grid grid-cols-[1fr_auto_auto_auto] gap-4 border-b border-border bg-secondary/50 px-4 py-2.5 text-xs font-medium text-muted-foreground">
|
||||
<span>Model</span>
|
||||
<span className="text-right">Tokens</span>
|
||||
<span className="text-right">Credits</span>
|
||||
<span className="text-right">Time</span>
|
||||
</div>
|
||||
{!usageLoading && !usageError && usageLoaded && (
|
||||
<div className="overflow-hidden rounded-lg border border-border">
|
||||
<div className="grid grid-cols-[minmax(0,1fr)_5.5rem_4.5rem_5.5rem] gap-4 border-b border-border bg-secondary/50 px-4 py-2.5 text-xs font-medium text-muted-foreground">
|
||||
<span>Model</span>
|
||||
<span className="text-right">Tokens</span>
|
||||
<span className="text-right">Credits</span>
|
||||
<span className="text-right">Time</span>
|
||||
</div>
|
||||
{usageTransactions.length === 0 ? (
|
||||
<p className="px-4 py-8 text-center text-sm text-muted-foreground">
|
||||
No usage transactions yet.
|
||||
</p>
|
||||
) : (
|
||||
<div className="divide-y divide-border">
|
||||
{usageTransactions.map((tx) => (
|
||||
<div
|
||||
key={tx.id}
|
||||
className="grid grid-cols-[1fr_auto_auto_auto] gap-4 px-4 py-3 text-sm hover:bg-surface-hover"
|
||||
className="grid grid-cols-[minmax(0,1fr)_5.5rem_4.5rem_5.5rem] gap-4 px-4 py-3 text-sm hover:bg-surface-hover"
|
||||
>
|
||||
<div className="min-w-0">
|
||||
<p className="font-medium text-foreground truncate">
|
||||
@@ -782,8 +781,19 @@ export function AccountView() {
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
<div className="flex justify-center border-t border-border px-4 py-3">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => void openExternalUrl(USAGE_DASHBOARD_URL)}
|
||||
className="flex items-center gap-1.5 rounded-md px-3 py-1.5 text-sm font-medium text-muted-foreground transition-colors hover:bg-surface-hover hover:text-foreground"
|
||||
>
|
||||
See More
|
||||
<ExternalLink className="h-3.5 w-3.5" />
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user