ef635276f9
* feat: rename task inline from the top bar via double-click Double-clicking the task title in the desktop top bar swaps it for an inline input; Enter commits the rename through the existing task update path, Escape or blur cancels. Archived tasks stay read-only. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * test: fix vi.fn generics in agent usage test for vitest 4 vi.fn dropped the two-type-argument form in the Vitest 4 upgrade; the old signature made mockResolvedValue infer never and fail typecheck. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix: ignore Enter during IME composition in topbar rename input Review follow-up: without the guard, the Enter that confirms an IME candidate committed the rename mid-typing. Mirrors the quick-chat tab rename input and the dialog default-action convention. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix: harden topbar rename commit path against IME 229 and mid-edit archive Independent-review follow-up: - also treat keyCode 229 as IME composition, matching the documented convention in dialog-default-action (some IMEs report the accepting Enter after isComposing flips false) - re-check isArchived on Enter so a task archived while the inline editor is open cannot be renamed from it Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * fix: make topbar rename title keyboard-operable Addresses PR #1702 review: the renameable title exposed role="link" with aria-disabled="false" but only a double-click handler. Add tabIndex, Enter to open the rename input, and focus restoration on keyboard exits. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> --------- Co-authored-by: Kandev Agent <agent@kandev.dev> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
83 lines
2.7 KiB
TypeScript
83 lines
2.7 KiB
TypeScript
import { cleanup, render, screen, waitFor } from "@testing-library/react";
|
|
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
import { TooltipProvider } from "@kandev/ui/tooltip";
|
|
import { AgentUsageSection } from "@/components/settings/agent-usage-section";
|
|
import type { AgentSubscriptionUsageResponse } from "@/lib/types/http";
|
|
|
|
const listAgentSubscriptionUsage = vi.hoisted(() =>
|
|
vi.fn<() => Promise<AgentSubscriptionUsageResponse>>(),
|
|
);
|
|
|
|
vi.mock("@/lib/api", () => ({
|
|
listAgentSubscriptionUsage,
|
|
}));
|
|
|
|
vi.mock("@/components/agent-logo", () => ({
|
|
AgentLogo: ({ agentName }: { agentName: string }) => <span data-testid={`logo-${agentName}`} />,
|
|
}));
|
|
|
|
afterEach(() => {
|
|
cleanup();
|
|
listAgentSubscriptionUsage.mockReset();
|
|
});
|
|
|
|
describe("AgentUsageSection", () => {
|
|
it("renders nothing when no subscription agents are present", async () => {
|
|
listAgentSubscriptionUsage.mockResolvedValue({ agents: [] });
|
|
|
|
const { container } = render(<AgentUsageSection />);
|
|
|
|
await waitFor(() => expect(listAgentSubscriptionUsage).toHaveBeenCalled());
|
|
expect(container.querySelector('[data-testid="agent-usage-section"]')).toBeNull();
|
|
});
|
|
|
|
it("renders utilization windows, plan badge, and per-agent errors", async () => {
|
|
listAgentSubscriptionUsage.mockResolvedValue({
|
|
agents: [
|
|
{
|
|
agent_id: "claude-acp",
|
|
display_name: "Claude Code",
|
|
usage: {
|
|
provider: "anthropic",
|
|
plan: "max",
|
|
windows: [
|
|
{
|
|
label: "5-hour",
|
|
utilization_pct: 62,
|
|
reset_at: new Date(Date.now() + 3600_000).toISOString(),
|
|
},
|
|
{
|
|
label: "7-day",
|
|
utilization_pct: 15,
|
|
reset_at: new Date(Date.now() + 86400_000).toISOString(),
|
|
},
|
|
],
|
|
fetched_at: new Date().toISOString(),
|
|
},
|
|
},
|
|
{
|
|
agent_id: "codex-acp",
|
|
display_name: "Codex",
|
|
error: "codex usage: unexpected status 500",
|
|
},
|
|
],
|
|
});
|
|
|
|
render(
|
|
<TooltipProvider>
|
|
<AgentUsageSection />
|
|
</TooltipProvider>,
|
|
);
|
|
|
|
await screen.findByTestId("agent-usage-section");
|
|
expect(screen.getByText("Claude Code")).toBeDefined();
|
|
expect(screen.getByText("max")).toBeDefined();
|
|
expect(screen.getByText("Good")).toBeDefined(); // worst window 62% → Good
|
|
expect(screen.getByText("5h")).toBeDefined();
|
|
expect(screen.getByText("62%")).toBeDefined();
|
|
expect(screen.getByText("7d")).toBeDefined();
|
|
expect(screen.getByText("Codex")).toBeDefined();
|
|
expect(screen.getByText("Could not fetch usage data.")).toBeDefined();
|
|
});
|
|
});
|