Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 77 additions & 31 deletions ui/litellm-dashboard/package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions ui/litellm-dashboard/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"private": true,
"scripts": {
"dev": "next dev",
"dev:webpack": "next dev --webpack",
"build": "next build",
"start": "next start",
"lint": "next lint",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,178 +3,151 @@ import { renderHook, waitFor } from "@testing-library/react";
import { useDisableShowPrompts } from "./useDisableShowPrompts";
import { LOCAL_STORAGE_EVENT } from "@/utils/localStorageUtils";

let mockPremiumUser: boolean | null = false;

vi.mock("@/app/(dashboard)/hooks/useAuthorized", () => ({
default: () => ({ premiumUser: mockPremiumUser }),
}));

describe("useDisableShowPrompts", () => {
const STORAGE_KEY = "disableShowPrompts";

beforeEach(() => {
localStorage.clear();
vi.clearAllMocks();
mockPremiumUser = false;
});

afterEach(() => {
localStorage.clear();
});

it("should return false when localStorage is empty", () => {
const { result } = renderHook(() => useDisableShowPrompts());

expect(result.current).toBe(false);
});

it("should return false when localStorage value is not 'true'", () => {
localStorage.setItem(STORAGE_KEY, "false");

const { result } = renderHook(() => useDisableShowPrompts());

expect(result.current).toBe(false);
});

it("should return true when localStorage value is 'true'", () => {
localStorage.setItem(STORAGE_KEY, "true");

const { result } = renderHook(() => useDisableShowPrompts());

expect(result.current).toBe(true);
});
describe("non-premium user", () => {
beforeEach(() => {
mockPremiumUser = false;
});

it("should return false when localStorage value is an empty string", () => {
localStorage.setItem(STORAGE_KEY, "");
it("should return false when localStorage is empty", () => {
const { result } = renderHook(() => useDisableShowPrompts());
expect(result.current).toBe(false);
});

const { result } = renderHook(() => useDisableShowPrompts());
it("should return false even when localStorage is 'true'", () => {
localStorage.setItem(STORAGE_KEY, "true");
const { result } = renderHook(() => useDisableShowPrompts());
expect(result.current).toBe(false);
});

expect(result.current).toBe(false);
it("should return false even when localStorage is 'false'", () => {
localStorage.setItem(STORAGE_KEY, "false");
const { result } = renderHook(() => useDisableShowPrompts());
expect(result.current).toBe(false);
});
});

it("should update when storage event fires for the correct key", async () => {
const { result } = renderHook(() => useDisableShowPrompts());

expect(result.current).toBe(false);

localStorage.setItem(STORAGE_KEY, "true");
const storageEvent = new StorageEvent("storage", {
key: STORAGE_KEY,
newValue: "true",
describe("premium user", () => {
beforeEach(() => {
mockPremiumUser = true;
});
window.dispatchEvent(storageEvent);

await waitFor(() => {
it("should return true when localStorage is empty (default on)", () => {
const { result } = renderHook(() => useDisableShowPrompts());
expect(result.current).toBe(true);
});
});

it("should not update when storage event fires for a different key", () => {
localStorage.setItem(STORAGE_KEY, "false");
const { result } = renderHook(() => useDisableShowPrompts());

expect(result.current).toBe(false);

const storageEvent = new StorageEvent("storage", {
key: "otherKey",
newValue: "true",
it("should return true when localStorage value is 'true'", () => {
localStorage.setItem(STORAGE_KEY, "true");
const { result } = renderHook(() => useDisableShowPrompts());
expect(result.current).toBe(true);
});
window.dispatchEvent(storageEvent);

expect(result.current).toBe(false);
});
it("should return false when localStorage value is 'false' (explicitly disabled)", () => {
localStorage.setItem(STORAGE_KEY, "false");
const { result } = renderHook(() => useDisableShowPrompts());
expect(result.current).toBe(false);
});

it("should update when custom LOCAL_STORAGE_EVENT fires for the correct key", async () => {
const { result } = renderHook(() => useDisableShowPrompts());
it("should update when storage event fires for the correct key", async () => {
const { result } = renderHook(() => useDisableShowPrompts());
expect(result.current).toBe(true); // default on

expect(result.current).toBe(false);
localStorage.setItem(STORAGE_KEY, "false");
const storageEvent = new StorageEvent("storage", {
key: STORAGE_KEY,
newValue: "false",
});
window.dispatchEvent(storageEvent);

localStorage.setItem(STORAGE_KEY, "true");
const customEvent = new CustomEvent(LOCAL_STORAGE_EVENT, {
detail: { key: STORAGE_KEY },
await waitFor(() => {
expect(result.current).toBe(false);
});
});
window.dispatchEvent(customEvent);

await waitFor(() => {
expect(result.current).toBe(true);
});
});

it("should not update when custom LOCAL_STORAGE_EVENT fires for a different key", () => {
localStorage.setItem(STORAGE_KEY, "false");
const { result } = renderHook(() => useDisableShowPrompts());
it("should not update when storage event fires for a different key", () => {
localStorage.setItem(STORAGE_KEY, "false");
const { result } = renderHook(() => useDisableShowPrompts());
expect(result.current).toBe(false);

expect(result.current).toBe(false);
const storageEvent = new StorageEvent("storage", {
key: "otherKey",
newValue: "true",
});
window.dispatchEvent(storageEvent);

const customEvent = new CustomEvent(LOCAL_STORAGE_EVENT, {
detail: { key: "otherKey" },
expect(result.current).toBe(false);
});
window.dispatchEvent(customEvent);

expect(result.current).toBe(false);
});

it("should update when localStorage changes from false to true via custom event", async () => {
localStorage.setItem(STORAGE_KEY, "false");
const { result } = renderHook(() => useDisableShowPrompts());
it("should update when custom LOCAL_STORAGE_EVENT fires for the correct key", async () => {
localStorage.setItem(STORAGE_KEY, "false");
const { result } = renderHook(() => useDisableShowPrompts());
expect(result.current).toBe(false);

expect(result.current).toBe(false);
localStorage.setItem(STORAGE_KEY, "true");
const customEvent = new CustomEvent(LOCAL_STORAGE_EVENT, {
detail: { key: STORAGE_KEY },
});
window.dispatchEvent(customEvent);

localStorage.setItem(STORAGE_KEY, "true");
const customEvent = new CustomEvent(LOCAL_STORAGE_EVENT, {
detail: { key: STORAGE_KEY },
await waitFor(() => {
expect(result.current).toBe(true);
});
});
window.dispatchEvent(customEvent);

await waitFor(() => {
expect(result.current).toBe(true);
});
});

it("should update when localStorage changes from true to false via storage event", async () => {
localStorage.setItem(STORAGE_KEY, "true");
const { result } = renderHook(() => useDisableShowPrompts());
it("should not update when custom LOCAL_STORAGE_EVENT fires for a different key", () => {
localStorage.setItem(STORAGE_KEY, "false");
const { result } = renderHook(() => useDisableShowPrompts());
expect(result.current).toBe(false);

expect(result.current).toBe(true);
const customEvent = new CustomEvent(LOCAL_STORAGE_EVENT, {
detail: { key: "otherKey" },
});
window.dispatchEvent(customEvent);

localStorage.setItem(STORAGE_KEY, "false");
const storageEvent = new StorageEvent("storage", {
key: STORAGE_KEY,
newValue: "false",
expect(result.current).toBe(false);
});
window.dispatchEvent(storageEvent);
});

await waitFor(() => {
describe("loading state (premiumUser is null)", () => {
it("should return false while premium status is loading", () => {
mockPremiumUser = null;
const { result } = renderHook(() => useDisableShowPrompts());
expect(result.current).toBe(false);
});
});

it("should cleanup event listeners on unmount", () => {
mockPremiumUser = true;
const addEventListenerSpy = vi.spyOn(window, "addEventListener");
const removeEventListenerSpy = vi.spyOn(window, "removeEventListener");

const { unmount } = renderHook(() => useDisableShowPrompts());

expect(addEventListenerSpy).toHaveBeenCalledTimes(2);
expect(addEventListenerSpy).toHaveBeenCalledWith("storage", expect.any(Function));
expect(addEventListenerSpy).toHaveBeenCalledWith(LOCAL_STORAGE_EVENT, expect.any(Function));

unmount();

expect(removeEventListenerSpy).toHaveBeenCalledTimes(2);
expect(removeEventListenerSpy).toHaveBeenCalledWith("storage", expect.any(Function));
expect(removeEventListenerSpy).toHaveBeenCalledWith(LOCAL_STORAGE_EVENT, expect.any(Function));
});

it("should handle multiple hooks independently", async () => {
const { result: result1 } = renderHook(() => useDisableShowPrompts());
const { result: result2 } = renderHook(() => useDisableShowPrompts());

expect(result1.current).toBe(false);
expect(result2.current).toBe(false);

localStorage.setItem(STORAGE_KEY, "true");
const customEvent = new CustomEvent(LOCAL_STORAGE_EVENT, {
detail: { key: STORAGE_KEY },
});
window.dispatchEvent(customEvent);

await waitFor(() => {
expect(result1.current).toBe(true);
expect(result2.current).toBe(true);
});
});
});
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
"use client";
// hooks/useDisableShowPrompts.ts
import { useSyncExternalStore } from "react";
import { getLocalStorageItem } from "@/utils/localStorageUtils";
import { LOCAL_STORAGE_EVENT } from "@/utils/localStorageUtils";
import useAuthorized from "@/app/(dashboard)/hooks/useAuthorized";

function subscribe(callback: () => void) {
const onStorage = (e: StorageEvent) => {
Expand All @@ -26,10 +28,21 @@ function subscribe(callback: () => void) {
};
}

function getSnapshot() {
return getLocalStorageItem("disableShowPrompts") === "true";
function getSnapshot(): string | null {
return getLocalStorageItem("disableShowPrompts");
}

export function useDisableShowPrompts() {
return useSyncExternalStore(subscribe, getSnapshot);
function getServerSnapshot(): string | null {
return null;
}

export function useDisableShowPrompts(): boolean {
const { premiumUser } = useAuthorized();
const storedRaw = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);

// Non-premium users always see prompts regardless of stored preference
if (!premiumUser) return false;

// Premium users: absent (null) or explicitly "true" means hidden; "false" means explicitly shown
return storedRaw === "true" || storedRaw === null;
}
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,13 @@ describe("UserDropdown", () => {

it("should toggle hide all prompts switch", async () => {
const user = userEvent.setup();
mockUseAuthorizedImpl = () => ({
userId: "test-user-id",
userEmail: "test@example.com",
userRole: "Admin",
premiumUser: true,
});
mockUseDisableShowPromptsImpl = () => false;
renderWithProviders(<UserDropdown onLogout={mockOnLogout} />);

await user.click(screen.getByText("User"));
Expand All @@ -207,6 +214,12 @@ describe("UserDropdown", () => {

it("should toggle hide all prompts switch off", async () => {
const user = userEvent.setup();
mockUseAuthorizedImpl = () => ({
userId: "test-user-id",
userEmail: "test@example.com",
userRole: "Admin",
premiumUser: true,
});
mockUseDisableShowPromptsImpl = () => true;
mockGetLocalStorageItemImpl = (key: string): string | null => {
if (key === "disableShowPrompts") return "true";
Expand All @@ -227,7 +240,7 @@ describe("UserDropdown", () => {
await user.click(toggle);

const localStorageUtils = vi.mocked(await import("@/utils/localStorageUtils"));
expect(localStorageUtils.removeLocalStorageItem).toHaveBeenCalledWith("disableShowPrompts");
expect(localStorageUtils.setLocalStorageItem).toHaveBeenCalledWith("disableShowPrompts", "false");
expect(localStorageUtils.emitLocalStorageChange).toHaveBeenCalledWith("disableShowPrompts");
});

Expand Down
Loading
Loading