import { describe, it, expect, vi, beforeEach } from "vitest"; import { render, screen, fireEvent } from "@testing-library/react"; import { UsageIndicator } from "./UsageIndicator"; import * as useUsageDataModule from "../hooks/useUsageData"; import type { ProviderUsage } from "../api"; // Mock the useUsageData hook vi.mock("../hooks/useUsageData", () => ({ useUsageData: vi.fn(), })); const mockUseUsageData = vi.mocked(useUsageDataModule.useUsageData); describe("UsageIndicator", () => { const mockOnClose = vi.fn(); const mockRefresh = vi.fn(); const mockProviders: ProviderUsage[] = [ { name: "Anthropic", icon: "🅰️", status: "ok", plan: "Pro", email: "user@example.com", windows: [ { label: "Session (5h)", percentUsed: 45, percentLeft: 55, resetText: "resets in 2h 15m", resetMs: 8100000, }, { label: "Weekly", percentUsed: 30, percentLeft: 70, resetText: "resets in 3d", resetMs: 259200000, }, ], }, { name: "OpenAI", icon: "🤖", status: "ok", windows: [ { label: "Hourly", percentUsed: 75, percentLeft: 25, resetText: "resets in 45m", resetMs: 2700000, }, ], }, { name: "Google", icon: "🔍", status: "no-auth", windows: [], }, ]; beforeEach(() => { vi.clearAllMocks(); // Clear localStorage to ensure clean view mode state localStorage.removeItem("kb-usage-view-mode"); }); it("renders nothing when isOpen is false", () => { mockUseUsageData.mockReturnValue({ providers: [], loading: false, error: null, lastUpdated: null, refresh: mockRefresh, }); const { container } = render(); expect(container.firstChild).toBeNull(); }); it("renders modal when isOpen is true", () => { mockUseUsageData.mockReturnValue({ providers: mockProviders, loading: false, error: null, lastUpdated: new Date(), refresh: mockRefresh, }); render(); expect(screen.getByTestId("usage-modal")).toBeInTheDocument(); expect(screen.getByText("Usage")).toBeInTheDocument(); }); it("renders provider cards with correct data", () => { mockUseUsageData.mockReturnValue({ providers: mockProviders, loading: false, error: null, lastUpdated: new Date(), refresh: mockRefresh, }); render(); // Check provider names are rendered expect(screen.getByText("Anthropic")).toBeInTheDocument(); expect(screen.getByText("OpenAI")).toBeInTheDocument(); expect(screen.getByText("Google")).toBeInTheDocument(); // Check status badges expect(screen.getAllByText("Connected").length).toBeGreaterThanOrEqual(1); expect(screen.getByText("Not configured")).toBeInTheDocument(); // Check usage windows expect(screen.getByText("Session (5h)")).toBeInTheDocument(); expect(screen.getByText("Weekly")).toBeInTheDocument(); expect(screen.getByText("Hourly")).toBeInTheDocument(); }); it("shows loading skeleton when loading", () => { mockUseUsageData.mockReturnValue({ providers: [], loading: true, error: null, lastUpdated: null, refresh: mockRefresh, }); render(); // Should show skeleton elements expect(document.querySelector(".usage-skeleton")).toBeInTheDocument(); }); it("shows error state when there is an error", () => { mockUseUsageData.mockReturnValue({ providers: [], loading: false, error: "Failed to fetch usage data", lastUpdated: null, refresh: mockRefresh, }); render(); expect(screen.getByText("Failed to load usage data")).toBeInTheDocument(); expect(screen.getByText("Failed to fetch usage data")).toBeInTheDocument(); expect(screen.getByText("Retry")).toBeInTheDocument(); }); it("shows skeleton when no providers (empty result after fetch completes)", () => { // When useUsageData completes its initial fetch and returns empty providers, // we now show the skeleton (not the empty state) to indicate we're waiting mockUseUsageData.mockReturnValue({ providers: [], loading: false, error: null, lastUpdated: null, refresh: mockRefresh, }); render(); // Should show skeleton because initial fetch completed but returned empty expect(document.querySelector(".usage-skeleton")).toBeInTheDocument(); }); it("calls refresh when refresh button clicked", async () => { mockUseUsageData.mockReturnValue({ providers: mockProviders, loading: false, error: null, lastUpdated: new Date(), refresh: mockRefresh, }); render(); const refreshBtn = screen.getByTestId("usage-refresh-btn"); fireEvent.click(refreshBtn); expect(mockRefresh).toHaveBeenCalledTimes(1); }); it("calls onClose when close button clicked", () => { mockUseUsageData.mockReturnValue({ providers: mockProviders, loading: false, error: null, lastUpdated: new Date(), refresh: mockRefresh, }); render(); const closeBtn = screen.getByTestId("usage-modal-close"); fireEvent.click(closeBtn); expect(mockOnClose).toHaveBeenCalledTimes(1); }); it("calls onClose when overlay is clicked", () => { mockUseUsageData.mockReturnValue({ providers: mockProviders, loading: false, error: null, lastUpdated: new Date(), refresh: mockRefresh, }); render(); const overlay = screen.getByTestId("usage-modal-overlay"); fireEvent.click(overlay); expect(mockOnClose).toHaveBeenCalledTimes(1); }); it("calls onClose when Escape key is pressed", () => { mockUseUsageData.mockReturnValue({ providers: mockProviders, loading: false, error: null, lastUpdated: new Date(), refresh: mockRefresh, }); render(); fireEvent.keyDown(document, { key: "Escape" }); expect(mockOnClose).toHaveBeenCalledTimes(1); }); it("renders progress bars with correct color classes", () => { mockUseUsageData.mockReturnValue({ providers: [ { name: "TestProvider", icon: "🧪", status: "ok", windows: [ { label: "Low Usage", percentUsed: 45, percentLeft: 55, resetText: "1h" }, { label: "Medium Usage", percentUsed: 75, percentLeft: 25, resetText: "2h" }, { label: "High Usage", percentUsed: 95, percentLeft: 5, resetText: "3h" }, ], }, ], loading: false, error: null, lastUpdated: new Date(), refresh: mockRefresh, }); render(); // Check progress bars exist with correct widths const progressBars = document.querySelectorAll(".usage-progress-fill"); expect(progressBars.length).toBe(3); // Check color classes are applied expect(document.querySelector(".usage-progress-fill--low")).toBeInTheDocument(); expect(document.querySelector(".usage-progress-fill--medium")).toBeInTheDocument(); expect(document.querySelector(".usage-progress-fill--high")).toBeInTheDocument(); }); it("disables refresh button when loading", () => { mockUseUsageData.mockReturnValue({ providers: mockProviders, loading: true, error: null, lastUpdated: new Date(), refresh: mockRefresh, }); render(); const refreshBtn = screen.getByTestId("usage-refresh-btn"); expect(refreshBtn).toBeDisabled(); }); it("passes autoRefresh option based on isOpen prop", () => { mockUseUsageData.mockReturnValue({ providers: [], loading: false, error: null, lastUpdated: null, refresh: mockRefresh, }); // Reset mock before testing mockUseUsageData.mockClear(); // When isOpen is true, autoRefresh should be true const { unmount } = render(); expect(mockUseUsageData).toHaveBeenCalledWith({ autoRefresh: true }); unmount(); // Reset mock mockUseUsageData.mockClear(); // When isOpen is false, autoRefresh should be false to prevent polling render(); // The hook is called even when isOpen is false because hooks must be called // unconditionally at the top level in React expect(mockUseUsageData).toHaveBeenCalledWith({ autoRefresh: false }); }); it("renders provider error messages", () => { mockUseUsageData.mockReturnValue({ providers: [ { name: "ErrorProvider", icon: "❌", status: "error", error: "Auth expired — run 'claude' to re-login", windows: [], }, ], loading: false, error: null, lastUpdated: new Date(), refresh: mockRefresh, }); render(); expect(screen.getByText("Error")).toBeInTheDocument(); expect(screen.getByText("Auth expired — run 'claude' to re-login")).toBeInTheDocument(); }); it("shows last updated timestamp", () => { const lastUpdated = new Date("2024-01-15T10:30:00"); mockUseUsageData.mockReturnValue({ providers: mockProviders, loading: false, error: null, lastUpdated, refresh: mockRefresh, }); render(); expect(screen.getByText(/Last updated:/)).toBeInTheDocument(); expect(screen.getByText(/10:30:00/)).toBeInTheDocument(); }); it("renders usage windows with correct percentage text", () => { mockUseUsageData.mockReturnValue({ providers: [ { name: "TestProvider", icon: "🧪", status: "ok", windows: [ { label: "Session", percentUsed: 45, percentLeft: 55, resetText: "resets in 2h" }, ], }, ], loading: false, error: null, lastUpdated: new Date(), refresh: mockRefresh, }); render(); expect(screen.getByText("45% used")).toBeInTheDocument(); expect(screen.getByText("55% left")).toBeInTheDocument(); expect(screen.getByText("resets in 2h")).toBeInTheDocument(); }); // View mode toggle tests it("renders view mode toggle buttons with correct initial state", () => { mockUseUsageData.mockReturnValue({ providers: mockProviders, loading: false, error: null, lastUpdated: new Date(), refresh: mockRefresh, }); render(); const usedBtn = screen.getByTestId("usage-view-toggle-used"); const remainingBtn = screen.getByTestId("usage-view-toggle-remaining"); expect(usedBtn).toBeInTheDocument(); expect(remainingBtn).toBeInTheDocument(); expect(usedBtn).toHaveClass("active"); expect(remainingBtn).not.toHaveClass("active"); }); it("switches view mode when toggle buttons are clicked", () => { mockUseUsageData.mockReturnValue({ providers: [ { name: "TestProvider", icon: "🧪", status: "ok", windows: [ { label: "Session", percentUsed: 45, percentLeft: 55, resetText: "resets in 2h" }, ], }, ], loading: false, error: null, lastUpdated: new Date(), refresh: mockRefresh, }); render(); const usedBtn = screen.getByTestId("usage-view-toggle-used"); const remainingBtn = screen.getByTestId("usage-view-toggle-remaining"); // Initially shows "used" view expect(screen.getByText("45% used")).toBeInTheDocument(); // Click remaining button fireEvent.click(remainingBtn); // Now should show "remaining" view expect(remainingBtn).toHaveClass("active"); expect(usedBtn).not.toHaveClass("active"); expect(screen.getByText("55% remaining")).toBeInTheDocument(); expect(screen.getByText("45% used")).toBeInTheDocument(); // Footer text // Click back to used fireEvent.click(usedBtn); expect(usedBtn).toHaveClass("active"); expect(remainingBtn).not.toHaveClass("active"); expect(screen.getByText("45% used")).toBeInTheDocument(); }); it("reads view mode from localStorage on mount", () => { // Set localStorage to 'remaining' before rendering localStorage.setItem("kb-usage-view-mode", "remaining"); mockUseUsageData.mockReturnValue({ providers: [ { name: "TestProvider", icon: "🧪", status: "ok", windows: [ { label: "Session", percentUsed: 45, percentLeft: 55, resetText: "resets in 2h" }, ], }, ], loading: false, error: null, lastUpdated: new Date(), refresh: mockRefresh, }); render(); const usedBtn = screen.getByTestId("usage-view-toggle-used"); const remainingBtn = screen.getByTestId("usage-view-toggle-remaining"); // Should initialize to 'remaining' from localStorage expect(remainingBtn).toHaveClass("active"); expect(usedBtn).not.toHaveClass("active"); expect(screen.getByText("55% remaining")).toBeInTheDocument(); // Clean up localStorage.removeItem("kb-usage-view-mode"); }); it("persists view mode to localStorage when changed", () => { mockUseUsageData.mockReturnValue({ providers: mockProviders, loading: false, error: null, lastUpdated: new Date(), refresh: mockRefresh, }); render(); const remainingBtn = screen.getByTestId("usage-view-toggle-remaining"); // Click remaining button fireEvent.click(remainingBtn); // Should save to localStorage expect(localStorage.getItem("kb-usage-view-mode")).toBe("remaining"); // Clean up localStorage.removeItem("kb-usage-view-mode"); }); // ProviderIcon integration tests it("renders SVG provider icons instead of emoji", () => { mockUseUsageData.mockReturnValue({ providers: [ { name: "Anthropic", icon: "🅰️", status: "ok", windows: [] }, { name: "OpenAI", icon: "🤖", status: "ok", windows: [] }, { name: "Google", icon: "🔍", status: "ok", windows: [] }, ], loading: false, error: null, lastUpdated: new Date(), refresh: mockRefresh, }); render(); // Should render SVG icons with correct provider data attributes expect(document.querySelector('[data-provider="anthropic"]')).toBeInTheDocument(); expect(document.querySelector('[data-provider="openai"]')).toBeInTheDocument(); expect(document.querySelector('[data-provider="google"]')).toBeInTheDocument(); }); it("maps Claude provider to anthropic icon", () => { mockUseUsageData.mockReturnValue({ providers: [ { name: "Claude", icon: "🅰️", status: "ok", windows: [] }, ], loading: false, error: null, lastUpdated: new Date(), refresh: mockRefresh, }); render(); expect(document.querySelector('[data-provider="anthropic"]')).toBeInTheDocument(); expect(document.querySelector("svg[aria-label='Anthropic']")).toBeInTheDocument(); }); it("maps Codex provider to openai icon", () => { mockUseUsageData.mockReturnValue({ providers: [ { name: "Codex", icon: "🤖", status: "ok", windows: [] }, ], loading: false, error: null, lastUpdated: new Date(), refresh: mockRefresh, }); render(); expect(document.querySelector('[data-provider="openai"]')).toBeInTheDocument(); expect(document.querySelector("svg[aria-label='OpenAI']")).toBeInTheDocument(); }); it("maps Gemini provider to google icon", () => { mockUseUsageData.mockReturnValue({ providers: [ { name: "Gemini", icon: "🔍", status: "ok", windows: [] }, ], loading: false, error: null, lastUpdated: new Date(), refresh: mockRefresh, }); render(); expect(document.querySelector('[data-provider="google"]')).toBeInTheDocument(); expect(document.querySelector("svg[aria-label='Google Gemini']")).toBeInTheDocument(); }); // Pace indicator tests it("renders pace marker for weekly windows with timing data", () => { mockUseUsageData.mockReturnValue({ providers: [ { name: "TestProvider", icon: "🧪", status: "ok", windows: [ { label: "Weekly", percentUsed: 30, percentLeft: 70, resetText: "resets in 3d", resetMs: 259200000, // 3 days remaining windowDurationMs: 604800000, // 7 days total pace: { status: "behind", percentElapsed: 57, message: "27% under pace", }, }, ], }, ], loading: false, error: null, lastUpdated: new Date(), refresh: mockRefresh, }); render(); const paceMarker = document.querySelector('[data-testid="pace-marker"]'); expect(paceMarker).toBeInTheDocument(); }); it("does not render pace marker for non-weekly windows (Session, Hourly)", () => { mockUseUsageData.mockReturnValue({ providers: [ { name: "TestProvider", icon: "🧪", status: "ok", windows: [ { label: "Session (5h)", percentUsed: 45, percentLeft: 55, resetText: "resets in 2h", resetMs: 7200000, windowDurationMs: 18000000, }, { label: "Hourly", percentUsed: 60, percentLeft: 40, resetText: "resets in 30m", resetMs: 1800000, windowDurationMs: 3600000, }, ], }, ], loading: false, error: null, lastUpdated: new Date(), refresh: mockRefresh, }); render(); const paceMarkers = document.querySelectorAll('[data-testid="pace-marker"]'); expect(paceMarkers.length).toBe(0); }); it("does not render pace marker when pace is undefined", () => { mockUseUsageData.mockReturnValue({ providers: [ { name: "TestProvider", icon: "🧪", status: "ok", windows: [ { label: "Weekly", percentUsed: 30, percentLeft: 70, resetText: "resets in 3d", // No pace field }, ], }, ], loading: false, error: null, lastUpdated: new Date(), refresh: mockRefresh, }); render(); const paceMarker = document.querySelector('[data-testid="pace-marker"]'); expect(paceMarker).not.toBeInTheDocument(); }); it("shows 'ahead of pace' text when usage exceeds elapsed time by >5%", () => { mockUseUsageData.mockReturnValue({ providers: [ { name: "TestProvider", icon: "🧪", status: "ok", windows: [ { label: "Weekly", percentUsed: 70, // 70% used percentLeft: 30, resetText: "resets in 3.5d", resetMs: 302400000, // 3.5 days remaining out of 7 windowDurationMs: 604800000, // 7 days total pace: { status: "ahead", percentElapsed: 50, message: "20% over pace", }, }, ], }, ], loading: false, error: null, lastUpdated: new Date(), refresh: mockRefresh, }); render(); const paceRow = screen.getByTestId("pace-row"); expect(paceRow).toHaveTextContent(/over pace/); expect(paceRow).toHaveTextContent("20%"); }); it("shows 'behind pace' text when usage is under elapsed time by >5%", () => { mockUseUsageData.mockReturnValue({ providers: [ { name: "TestProvider", icon: "🧪", status: "ok", windows: [ { label: "Weekly", percentUsed: 20, // 20% used percentLeft: 80, resetText: "resets in 3.5d", resetMs: 302400000, // 3.5 days remaining out of 7 windowDurationMs: 604800000, // 7 days total pace: { status: "behind", percentElapsed: 50, message: "30% under pace", }, }, ], }, ], loading: false, error: null, lastUpdated: new Date(), refresh: mockRefresh, }); render(); const paceRow = screen.getByTestId("pace-row"); expect(paceRow).toHaveTextContent(/under pace/); expect(paceRow).toHaveTextContent("30%"); }); it("shows 'on pace' text when usage is within 5% of elapsed time", () => { mockUseUsageData.mockReturnValue({ providers: [ { name: "TestProvider", icon: "🧪", status: "ok", windows: [ { label: "Weekly", percentUsed: 52, // 52% used percentLeft: 48, resetText: "resets in 3.5d", resetMs: 302400000, // 3.5 days remaining out of 7 windowDurationMs: 604800000, // 7 days total pace: { status: "on-track", percentElapsed: 50, message: "On pace with time elapsed", }, }, ], }, ], loading: false, error: null, lastUpdated: new Date(), refresh: mockRefresh, }); render(); const paceRow = screen.getByTestId("pace-row"); expect(paceRow).toHaveTextContent(/On pace/); }); it("pace marker position inverts correctly when switching to remaining mode", () => { // Mock provider with weekly window mockUseUsageData.mockReturnValue({ providers: [ { name: "TestProvider", icon: "🧪", status: "ok", windows: [ { label: "Weekly", percentUsed: 30, percentLeft: 70, resetText: "resets in 3.5d", resetMs: 302400000, // 50% elapsed windowDurationMs: 604800000, pace: { status: "behind", percentElapsed: 50, message: "20% under pace", }, }, ], }, ], loading: false, error: null, lastUpdated: new Date(), refresh: mockRefresh, }); render(); // In used mode: marker at 50% let paceMarker = document.querySelector('[data-testid="pace-marker"]') as HTMLElement; expect(paceMarker).toBeInTheDocument(); expect(paceMarker.style.left).toBe("50%"); // Switch to remaining mode const remainingBtn = screen.getByTestId("usage-view-toggle-remaining"); fireEvent.click(remainingBtn); // In remaining mode: marker at 100 - 50 = 50% (same in this case since it's 50/50) paceMarker = document.querySelector('[data-testid="pace-marker"]') as HTMLElement; expect(paceMarker.style.left).toBe("50%"); }); it("pace percentage text uses backend message directly", () => { // Clear localStorage to ensure fresh 'used' mode localStorage.removeItem("kb-usage-view-mode"); // Setup: 70% used (ahead of pace), 30% remaining mockUseUsageData.mockReturnValue({ providers: [ { name: "TestProvider", icon: "🧪", status: "ok", windows: [ { label: "Weekly", percentUsed: 70, // 70% used percentLeft: 30, resetText: "resets in 3.5d", resetMs: 302400000, // 50% elapsed windowDurationMs: 604800000, pace: { status: "ahead", percentElapsed: 50, message: "20% over pace", }, }, ], }, ], loading: false, error: null, lastUpdated: new Date(), refresh: mockRefresh, }); render(); // In used mode: ahead of pace (70% used vs 50% elapsed) let paceRow = screen.getByTestId("pace-row"); expect(paceRow).toHaveTextContent(/over pace/); // Switch to remaining mode - message stays the same (from backend) const remainingBtn = screen.getByTestId("usage-view-toggle-remaining"); fireEvent.click(remainingBtn); // The message comes from backend, so it doesn't change based on view mode paceRow = screen.getByTestId("pace-row"); expect(paceRow).toHaveTextContent("20% over pace"); }); // Refresh-on-open behavior tests it("calls refresh when isOpen transitions from false to true", () => { mockUseUsageData.mockReturnValue({ providers: mockProviders, loading: false, error: null, lastUpdated: null, // No recent update, should refresh refresh: mockRefresh, }); const { rerender } = render(); // Initially isOpen is false, refresh should not be called expect(mockRefresh).not.toHaveBeenCalled(); // Open the modal - isOpen transitions to true rerender(); // refresh should be called when modal opens expect(mockRefresh).toHaveBeenCalledTimes(1); }); it("does not call refresh when isOpen is already true on mount", () => { mockUseUsageData.mockReturnValue({ providers: mockProviders, loading: false, error: null, lastUpdated: new Date(), refresh: mockRefresh, }); // Render with isOpen=true initially render(); // refresh should NOT be called on initial mount when isOpen is already true // (the hook will handle initial data fetch) expect(mockRefresh).not.toHaveBeenCalled(); }); it("does not call refresh when isOpen becomes false (modal closes)", () => { mockUseUsageData.mockReturnValue({ providers: mockProviders, loading: false, error: null, lastUpdated: new Date(), refresh: mockRefresh, }); const { rerender } = render(); // Clear any calls from initial render mockRefresh.mockClear(); // Close the modal - isOpen transitions to false rerender(); // refresh should NOT be called when modal closes expect(mockRefresh).not.toHaveBeenCalled(); }); it("skips refresh if data was updated within last 5 seconds", () => { // Data was just updated (within 5 seconds) const recentUpdate = new Date(Date.now() - 2000); // 2 seconds ago mockUseUsageData.mockReturnValue({ providers: mockProviders, loading: false, error: null, lastUpdated: recentUpdate, refresh: mockRefresh, }); const { rerender } = render(); // Clear any calls from initial render mockRefresh.mockClear(); // Open the modal rerender(); // refresh should NOT be called because data is fresh (within 5 seconds) expect(mockRefresh).not.toHaveBeenCalled(); }); it("calls refresh when modal opens if data is stale (older than 5 seconds)", () => { // Data was updated 10 seconds ago (stale) const staleUpdate = new Date(Date.now() - 10000); // 10 seconds ago mockUseUsageData.mockReturnValue({ providers: mockProviders, loading: false, error: null, lastUpdated: staleUpdate, refresh: mockRefresh, }); const { rerender } = render(); // Clear any calls from initial render mockRefresh.mockClear(); // Open the modal rerender(); // refresh should be called because data is stale (older than 5 seconds) expect(mockRefresh).toHaveBeenCalledTimes(1); }); it("calls refresh again when modal reopens after being closed", () => { mockUseUsageData.mockReturnValue({ providers: mockProviders, loading: false, error: null, lastUpdated: null, refresh: mockRefresh, }); const { rerender } = render(); // Open the modal first time rerender(); expect(mockRefresh).toHaveBeenCalledTimes(1); // Close the modal rerender(); // Reopen the modal rerender(); // refresh should be called again on reopen expect(mockRefresh).toHaveBeenCalledTimes(2); }); // Initial loading state tests it("shows skeleton when modal opens with no cached data", () => { // Simulate: initial fetch has not completed, providers array is empty mockUseUsageData.mockReturnValue({ providers: [], loading: false, // loading is false, but no providers yet error: null, lastUpdated: null, refresh: mockRefresh, }); render(); // Should show skeleton because initial fetch hasn't completed expect(document.querySelector(".usage-skeleton")).toBeInTheDocument(); }); it("shows skeleton when modal reopens after being closed with data", () => { const { rerender } = render(); // First open: show data mockUseUsageData.mockReturnValue({ providers: mockProviders, loading: false, error: null, lastUpdated: new Date(), refresh: mockRefresh, }); rerender(); // Should show providers, not skeleton expect(screen.getByText("Anthropic")).toBeInTheDocument(); expect(document.querySelector(".usage-skeleton")).not.toBeInTheDocument(); // Close the modal rerender(); // Reopen with no providers (simulating stale state before fetch completes) mockUseUsageData.mockReturnValue({ providers: [], // Empty - will show skeleton until fetch completes loading: false, error: null, lastUpdated: null, refresh: mockRefresh, }); rerender(); // Should show skeleton on reopen until providers arrive expect(document.querySelector(".usage-skeleton")).toBeInTheDocument(); }); it("shows providers once data arrives after initial skeleton", () => { // First render: empty providers, no data yet mockUseUsageData.mockReturnValue({ providers: [], loading: false, error: null, lastUpdated: null, refresh: mockRefresh, }); const { rerender } = render(); // Should show skeleton initially expect(document.querySelector(".usage-skeleton")).toBeInTheDocument(); // Simulate data arriving mockUseUsageData.mockReturnValue({ providers: mockProviders, loading: false, error: null, lastUpdated: new Date(), refresh: mockRefresh, }); // Trigger a re-render with new data rerender(); // Now should show providers expect(screen.getByText("Anthropic")).toBeInTheDocument(); expect(document.querySelector(".usage-skeleton")).not.toBeInTheDocument(); }); // Percentage rounding tests it("rounds percentage values in display text (whole numbers)", () => { // Use decimal percentages to verify rounding mockUseUsageData.mockReturnValue({ providers: [ { name: "TestProvider", icon: "🧪", status: "ok", windows: [ { label: "Session", percentUsed: 45.678, percentLeft: 54.322, resetText: "resets in 2h" }, ], }, ], loading: false, error: null, lastUpdated: new Date(), refresh: mockRefresh, }); render(); // Should display rounded values: 46% used, 54% left expect(screen.getByText("46% used")).toBeInTheDocument(); expect(screen.getByText("54% left")).toBeInTheDocument(); }); it("rounds percentage values in remaining view mode", () => { // Use decimal percentages to verify rounding mockUseUsageData.mockReturnValue({ providers: [ { name: "TestProvider", icon: "🧪", status: "ok", windows: [ { label: "Session", percentUsed: 33.333, percentLeft: 66.667, resetText: "resets in 3h" }, ], }, ], loading: false, error: null, lastUpdated: new Date(), refresh: mockRefresh, }); render(); // Switch to remaining mode const remainingBtn = screen.getByTestId("usage-view-toggle-remaining"); fireEvent.click(remainingBtn); // Should display rounded values: 67% remaining, 33% used expect(screen.getByText("67% remaining")).toBeInTheDocument(); expect(screen.getByText("33% used")).toBeInTheDocument(); }); it("rounds percentage values in progress bar width", () => { mockUseUsageData.mockReturnValue({ providers: [ { name: "TestProvider", icon: "🧪", status: "ok", windows: [ { label: "Session", percentUsed: 45.678, percentLeft: 54.322, resetText: "resets in 2h" }, ], }, ], loading: false, error: null, lastUpdated: new Date(), refresh: mockRefresh, }); render(); // Check progress bar width is rounded const progressBar = document.querySelector(".usage-progress-fill") as HTMLElement; expect(progressBar).toBeInTheDocument(); expect(progressBar.style.width).toBe("46%"); // 45.678 rounds to 46 }); });