mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-10 22:41:41 +00:00
fix(ui): gate dashboard layout on ui config load so deep links work under SERVER_ROOT_PATH (#30312)
* fix(ui): gate dashboard layout on ui config load so deep links work under SERVER_ROOT_PATH * test(ui): create ui config deferred per test so the pending state stays repeatable
This commit is contained in:
parent
40301820e7
commit
76b4c4b111
2 changed files with 83 additions and 1 deletions
78
ui/litellm-dashboard/src/app/(dashboard)/layout.test.tsx
Normal file
78
ui/litellm-dashboard/src/app/(dashboard)/layout.test.tsx
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
import { describe, it, expect, vi, beforeEach } from "vitest";
|
||||
import { render, screen, waitFor } from "@testing-library/react";
|
||||
import { AuthProvider } from "@/contexts/AuthContext";
|
||||
import Layout from "./layout";
|
||||
|
||||
vi.mock("next/navigation", () => ({
|
||||
useRouter: vi.fn(() => ({ push: vi.fn(), replace: vi.fn() })),
|
||||
useSearchParams: vi.fn(() => new URLSearchParams()),
|
||||
usePathname: vi.fn(() => "/ui/guardrails"),
|
||||
}));
|
||||
|
||||
vi.mock("@/components/navbar", () => ({
|
||||
default: () => <div data-testid="navbar" />,
|
||||
}));
|
||||
|
||||
vi.mock("@/app/(dashboard)/components/SidebarProvider", () => ({
|
||||
default: () => <div data-testid="sidebar" />,
|
||||
}));
|
||||
|
||||
vi.mock("@/components/DebugWarningBanner", () => ({
|
||||
DebugWarningBanner: () => null,
|
||||
}));
|
||||
|
||||
vi.mock("@/contexts/ThemeContext", () => ({
|
||||
ThemeProvider: ({ children }: { children: React.ReactNode }) => <>{children}</>,
|
||||
}));
|
||||
|
||||
vi.mock("@/components/common_components/LoadingScreen", () => ({
|
||||
default: () => <div data-testid="loading-screen" />,
|
||||
}));
|
||||
|
||||
type Deferred = { promise: Promise<void>; resolve: () => void };
|
||||
|
||||
const createDeferred = (): Deferred => {
|
||||
let resolve!: () => void;
|
||||
const promise = new Promise<void>((r) => {
|
||||
resolve = r;
|
||||
});
|
||||
return { promise, resolve };
|
||||
};
|
||||
|
||||
let pendingUiConfig: Deferred;
|
||||
|
||||
vi.mock("@/components/networking", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("@/components/networking")>();
|
||||
return {
|
||||
...actual,
|
||||
getUiConfig: vi.fn(() => pendingUiConfig.promise),
|
||||
setGlobalLitellmHeaderName: vi.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
describe("(dashboard) Layout", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
pendingUiConfig = createDeferred();
|
||||
});
|
||||
|
||||
it("does not mount route content until getUiConfig has resolved", async () => {
|
||||
render(
|
||||
<AuthProvider>
|
||||
<Layout>
|
||||
<div data-testid="page-content" />
|
||||
</Layout>
|
||||
</AuthProvider>,
|
||||
);
|
||||
|
||||
await waitFor(() => expect(screen.getByTestId("loading-screen")).toBeTruthy());
|
||||
expect(screen.queryByTestId("page-content")).toBeNull();
|
||||
expect(screen.queryByTestId("navbar")).toBeNull();
|
||||
|
||||
pendingUiConfig.resolve();
|
||||
|
||||
await waitFor(() => expect(screen.getByTestId("page-content")).toBeTruthy());
|
||||
expect(screen.getByTestId("navbar")).toBeTruthy();
|
||||
expect(screen.queryByTestId("loading-screen")).toBeNull();
|
||||
});
|
||||
});
|
||||
|
|
@ -45,9 +45,13 @@ function DashboardShell({ children }: { children: React.ReactNode }) {
|
|||
|
||||
function LayoutContent({ children }: { children: React.ReactNode }) {
|
||||
const searchParams = useSearchParams();
|
||||
const { accessToken } = useAuth();
|
||||
const { accessToken, authLoading } = useAuth();
|
||||
const isInvitationFlow = Boolean(searchParams.get("invitation_id"));
|
||||
|
||||
if (authLoading) {
|
||||
return <LoadingScreen />;
|
||||
}
|
||||
|
||||
return (
|
||||
<ThemeProvider accessToken={accessToken}>
|
||||
{isInvitationFlow ? children : <DashboardShell>{children}</DashboardShell>}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue