litellm/ui/litellm-dashboard/tests/test-utils.tsx
jesus-berri 8efb99b4ac
feat(ui): shareable URL state for usage views and users-to-usage deeplinks (#43425)
* feat(ui): shareable URL state for usage views and users-to-usage deeplinks

The Usage page now keeps the view, global view tab, date range and entity filters in the
URL, so links can be bookmarked and shared. Relative presets are stored as range=7d and
keep rolling, custom ranges as UTC from/to. Invalid params are dropped, and a link to a
view or user the viewer cannot access shows a toast and falls back to the default view.

Internal Users rows get a "View usage" action that opens usage filtered to that user, and
the per-user usage views link back to the user's record. A "Copy Share Link" button sits
next to "Ask AI"

* fix(ui): clean usage links for sessions without the organizations query

The organizations query only runs for premium sessions, and a disabled query stays
pending forever, so the URL cleanup never ran for other sessions. Gate on isLoading
instead, and give the access denied toast an id so a re-run effect shows it once

* fix(ui): type-check usage entity filters and address review findings

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(ui): keep team usage available to internal users

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* style(ui): format users table usage action

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(ui): sync date picker preset with the current usage range

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(ui): restore dashboard AGENTS.md from main and drop unused usage state

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(ui): share custom usage date ranges as calendar days

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(ui): keep per-user agent usage page across rerenders

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
2026-10-16 16:42:29 -07:00

68 lines
2.3 KiB
TypeScript

import React, { PropsWithChildren } from "react";
import { render, RenderOptions, screen, waitFor } from "@testing-library/react";
import type userEvent from "@testing-library/user-event";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { NuqsTestingAdapter, OnUrlUpdateFunction } from "nuqs/adapters/testing";
import { HotkeysProvider } from "react-hotkeys-hook";
import { expect } from "vitest";
// Create a client for testing
export const testQueryClient = new QueryClient({
defaultOptions: {
queries: {
retry: false,
gcTime: Infinity,
staleTime: Infinity,
refetchOnWindowFocus: false,
refetchOnReconnect: false,
refetchOnMount: false,
},
mutations: {
retry: false,
},
},
});
interface ProviderOptions {
searchParams?: string | Record<string, string> | URLSearchParams;
onUrlUpdate?: OnUrlUpdateFunction;
resetUrlUpdateQueueOnMount?: boolean;
}
export const renderWithProviders = (ui: React.ReactElement, options?: RenderOptions & ProviderOptions) => {
const { searchParams, onUrlUpdate, resetUrlUpdateQueueOnMount, ...renderOptions } = options ?? {};
const Providers: React.FC<PropsWithChildren> = ({ children }) => (
<NuqsTestingAdapter
searchParams={searchParams}
onUrlUpdate={onUrlUpdate}
resetUrlUpdateQueueOnMount={resetUrlUpdateQueueOnMount}
hasMemory
>
<QueryClientProvider client={testQueryClient}>
<HotkeysProvider>{children}</HotkeysProvider>
</QueryClientProvider>
</NuqsTestingAdapter>
);
return render(ui, { wrapper: Providers, ...renderOptions });
};
const pointerBlocked = (element: HTMLElement): boolean => {
for (let node: HTMLElement | null = element; node !== null; node = node.parentElement) {
if (node.style.pointerEvents === "none") return true;
}
return false;
};
export const chooseSelectOption = async (
user: Pick<ReturnType<typeof userEvent.setup>, "click">,
trigger: HTMLElement,
optionName: string | RegExp,
role: "option" | "menuitem" | "menuitemradio" = "option",
) => {
await user.click(trigger);
const option = await screen.findByRole(role, { name: optionName });
await waitFor(() => expect(pointerBlocked(option)).toBe(false));
await user.click(option);
};
export * from "@testing-library/react";