feat(FN-826): add usage dialog with reset time visibility and Claude 5h tests

- Add Usage Dialog section to README dashboard features documentation
- Enhance reset time visibility in the UsageIndicator component
- Add comprehensive tests for Claude 5h usage scenarios (155 lines)
- Update dashboard styles for usage indicator improvements
This commit is contained in:
gsxdsm
2026-04-04 09:51:38 -07:00
parent 866b97a1c2
commit 0207959552
3 changed files with 169 additions and 1 deletions

View File

@@ -1281,4 +1281,159 @@ describe("UsageIndicator", () => {
expect(screen.getByText("resets in 3d")).toBeInTheDocument();
expect(document.querySelector(".usage-window-reset-at")).toBeInTheDocument();
});
it("Claude 5h session row shows both relative reset text and absolute reset time", () => {
// Simulate a realistic Claude usage payload with Session (5h) + Weekly windows
const sessionResetAt = new Date(Date.now() + 2 * 60 * 60 * 1000); // 2h from now
const weeklyResetAt = new Date(Date.now() + 5 * 24 * 60 * 60 * 1000); // 5d from now
mockUseUsageData.mockReturnValue({
providers: [
{
name: "Claude",
icon: "🟠",
status: "ok",
plan: "Pro",
windows: [
{
label: "Session (5h)",
percentUsed: 45,
percentLeft: 55,
resetText: "resets in 2h",
resetMs: 7200000,
resetAt: sessionResetAt.toISOString(),
},
{
label: "Weekly",
percentUsed: 30,
percentLeft: 70,
resetText: "resets in 5d",
resetMs: 432000000,
resetAt: weeklyResetAt.toISOString(),
},
],
},
],
loading: false,
error: null,
lastUpdated: new Date(),
refresh: mockRefresh,
});
render(<UsageIndicator isOpen={true} onClose={mockOnClose} />);
// Session (5h) row should exist
expect(screen.getByText("Session (5h)")).toBeInTheDocument();
// Both relative reset texts should be present
expect(screen.getByText("resets in 2h")).toBeInTheDocument();
expect(screen.getByText("resets in 5d")).toBeInTheDocument();
// Both absolute reset times should be rendered
const resetAtElements = document.querySelectorAll(".usage-window-reset-at");
expect(resetAtElements.length).toBe(2);
// Session row should show the absolute time formatted for today (just time like "2:30 PM")
// Since the resetAt is today, formatResetAt should return just the time string
const sessionResetAtEl = resetAtElements[0];
expect(sessionResetAtEl.textContent).toBeTruthy();
// It should be a time-only format (e.g., "12:30 PM") since the reset is today
expect(sessionResetAtEl.textContent).toMatch(/\d{1,2}:\d{2}\s*(AM|PM)/i);
// Verify provider card shows plan
expect(screen.getByText("Pro")).toBeInTheDocument();
});
it("Claude 5h session row without resetAt only shows relative reset text", () => {
mockUseUsageData.mockReturnValue({
providers: [
{
name: "Claude",
icon: "🟠",
status: "ok",
windows: [
{
label: "Session (5h)",
percentUsed: 60,
percentLeft: 40,
resetText: "resets in 1h 30m",
resetMs: 5400000,
// No resetAt — backend couldn't infer the exact timestamp
},
],
},
],
loading: false,
error: null,
lastUpdated: new Date(),
refresh: mockRefresh,
});
render(<UsageIndicator isOpen={true} onClose={mockOnClose} />);
// Session (5h) should still be visible
expect(screen.getByText("Session (5h)")).toBeInTheDocument();
// Relative reset text should be present
expect(screen.getByText("resets in 1h 30m")).toBeInTheDocument();
// No absolute reset time element
expect(document.querySelector(".usage-window-reset-at")).not.toBeInTheDocument();
});
it("non-Claude providers are unaffected by resetAt display feature", () => {
const resetAt = new Date(Date.now() + 2 * 60 * 60 * 1000);
mockUseUsageData.mockReturnValue({
providers: [
{
name: "Codex",
icon: "🟢",
status: "ok",
windows: [
{
label: "Session (5h)",
percentUsed: 20,
percentLeft: 80,
resetText: "resets in 4h",
resetMs: 14400000,
// Codex also supports resetAt from its API
resetAt: resetAt.toISOString(),
},
],
},
{
name: "Gemini",
icon: "🔵",
status: "ok",
windows: [
{
label: "Flash models",
percentUsed: 10,
percentLeft: 90,
resetText: "resets in 6h",
resetMs: 21600000,
// No resetAt for Gemini
},
],
},
],
loading: false,
error: null,
lastUpdated: new Date(),
refresh: mockRefresh,
});
render(<UsageIndicator isOpen={true} onClose={mockOnClose} />);
// Both providers should render normally
expect(screen.getByText("Codex")).toBeInTheDocument();
expect(screen.getByText("Gemini")).toBeInTheDocument();
expect(screen.getByText("Flash models")).toBeInTheDocument();
// Codex should show absolute reset time (since it provides resetAt)
expect(document.querySelectorAll(".usage-window-reset-at").length).toBe(1);
// Both providers should show their relative reset text
expect(screen.getByText("resets in 4h")).toBeInTheDocument();
expect(screen.getByText("resets in 6h")).toBeInTheDocument();
});
});

View File

@@ -11002,8 +11002,11 @@ html .column.drag-over * {
.usage-window-reset-at {
font-size: 11px;
color: var(--text-dim);
color: var(--text-muted);
white-space: nowrap;
background: var(--bg-tertiary);
padding: 1px 5px;
border-radius: 3px;
}
/* Pace marker and text */